├── .gitignore ├── color.go ├── js ├── doc.go ├── value.go ├── js_host_test.go ├── funcs_test.go ├── js_test.go ├── jstest │ ├── docker.go │ ├── build.go │ ├── nodejs.go │ └── chrome.go ├── js_wasm.go ├── json_test.go ├── json.go ├── bytes.go ├── promise.go ├── promise_test.go ├── js_host.go ├── funcs.go └── js.go ├── examples ├── webrtc │ ├── run.sh │ └── main.go ├── grpc-over-ws │ ├── run.sh │ ├── protocol │ │ ├── hello.proto │ │ ├── protocol.go │ │ └── hello.pb.go │ ├── index.html │ ├── server.go │ └── client.go ├── go.mod └── go.sum ├── net ├── ws │ ├── common.go │ ├── wsconn_js_test.go │ ├── wsconn_test.go │ ├── wsconn.go │ └── wsconn_js.go └── webrtc │ ├── signalling.go │ ├── active.go │ ├── webrtc.go │ ├── peer_chan.go │ ├── passive.go │ └── peer_conn.go ├── .travis.yml ├── extension └── chrome │ ├── chrome.go │ ├── bookmarks.go │ ├── native │ ├── native_host.go │ └── native_js.go │ └── tabs.go ├── tokenList.go ├── internal └── goenv │ └── goenv.go ├── storage ├── json.go ├── common.go └── storage.go ├── style.go ├── global.go ├── require ├── require_test.go ├── require_js_test.go └── require.go ├── shadowRoot.go ├── input.go ├── window.go ├── units.go ├── document.go ├── go.mod ├── README.md ├── cmd ├── app │ └── main.go └── wasm-server │ └── main.go ├── node.go ├── event.go ├── go.sum ├── svg └── svg.go ├── element.go ├── htmlElement.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.wat 3 | *.wast -------------------------------------------------------------------------------- /color.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | type Color string 4 | -------------------------------------------------------------------------------- /js/doc.go: -------------------------------------------------------------------------------- 1 | // Package JS provides additional functionality on top of syscall/js package for WASM. 2 | package js 3 | -------------------------------------------------------------------------------- /examples/webrtc/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # should be run from the root of the repository 4 | wasm-server --apps ./examples --main webrtc -------------------------------------------------------------------------------- /js/value.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | // Type is an analog for JS "typeof" operator. 4 | func (v Value) Type() Type { 5 | return v.Ref.Type() 6 | } 7 | -------------------------------------------------------------------------------- /examples/grpc-over-ws/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | WASM_FILES="$(go env GOROOT)/misc/wasm" 3 | GOOS=js GOARCH=wasm go build -o client.wasm ./client.go || exit 1 4 | cp ${WASM_FILES}/wasm_exec.js ./ 5 | go run ./server.go -------------------------------------------------------------------------------- /js/js_host_test.go: -------------------------------------------------------------------------------- 1 | //+build !js 2 | 3 | package js 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/dennwc/dom/js/jstest" 9 | ) 10 | 11 | func TestJS(t *testing.T) { 12 | jstest.RunTestNodeJS(t) 13 | } 14 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dennwc/dom/examples 2 | 3 | require ( 4 | github.com/dennwc/dom v0.2.1 5 | github.com/gogo/protobuf v1.1.1 6 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a 7 | google.golang.org/grpc v1.16.0 8 | ) 9 | 10 | replace github.com/dennwc/dom => ../ 11 | -------------------------------------------------------------------------------- /js/funcs_test.go: -------------------------------------------------------------------------------- 1 | //+build wasm,js 2 | 3 | package js 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestNewFuncJS(t *testing.T) { 12 | v := NativeFuncOf("a", "b", `return a+b`) 13 | c := int(v.Invoke(1, 2).Int()) 14 | require.Equal(t, 3, c) 15 | } 16 | -------------------------------------------------------------------------------- /net/ws/common.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import "net" 4 | 5 | var _ net.Addr = wsAddr{} 6 | 7 | type wsAddr struct { 8 | } 9 | 10 | func (wsAddr) Network() string { 11 | return "ws" 12 | } 13 | 14 | func (wsAddr) String() string { 15 | // TODO: proper address, if possible 16 | return "ws://localhost" 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.12.x" 5 | - tip 6 | 7 | dist: xenial 8 | sudo: required 9 | services: 10 | - docker 11 | 12 | matrix: 13 | fast_finish: true 14 | allow_failures: 15 | - go: tip 16 | 17 | env: 18 | - GO111MODULE=on GO_TESTPROXY_DEBUG=true 19 | 20 | install: 21 | - go mod download 22 | 23 | script: 24 | - go test -v ./... -------------------------------------------------------------------------------- /js/js_test.go: -------------------------------------------------------------------------------- 1 | //+build wasm,js 2 | 3 | package js 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestUndefined(t *testing.T) { 12 | var v Value 13 | require.True(t, v.IsUndefined()) 14 | } 15 | 16 | func TestEmptyErrorObj(t *testing.T) { 17 | e := Error{NewObject().Ref} 18 | require.Equal(t, "JavaScript error: undefined", e.Error()) 19 | } 20 | -------------------------------------------------------------------------------- /extension/chrome/chrome.go: -------------------------------------------------------------------------------- 1 | package chrome 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | var ( 6 | chrome = js.Get("chrome") 7 | runtime = chrome.Get("runtime") 8 | ) 9 | 10 | type WindowID int 11 | 12 | const CurrentWindow = WindowID(0) 13 | 14 | func lastError() error { 15 | e := runtime.Get("lastError") 16 | if !e.Valid() { 17 | return nil 18 | } 19 | return js.Error{Value: e.JSValue()} 20 | } 21 | -------------------------------------------------------------------------------- /tokenList.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | func AsTokenList(v js.Value) *TokenList { 6 | if !v.Valid() { 7 | return nil 8 | } 9 | return &TokenList{v: v} 10 | } 11 | 12 | type TokenList struct { 13 | v js.Value 14 | } 15 | 16 | func (t *TokenList) Add(class ...interface{}) { 17 | t.v.Call("add", class...) 18 | } 19 | 20 | func (t *TokenList) Remove(class ...interface{}) { 21 | t.v.Call("remove", class...) 22 | } 23 | -------------------------------------------------------------------------------- /internal/goenv/goenv.go: -------------------------------------------------------------------------------- 1 | //+build !js 2 | 3 | package goenv 4 | 5 | import ( 6 | "bytes" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | ) 11 | 12 | func GOROOT() string { 13 | if s := os.Getenv("GOROOT"); s != "" { 14 | return s 15 | } 16 | out, err := exec.Command("go", "env", "GOROOT").Output() 17 | if err != nil { 18 | panic(err) 19 | } 20 | return string(bytes.TrimSpace(out)) 21 | } 22 | 23 | func Go() string { 24 | return filepath.Join(GOROOT(), "bin/go") 25 | } 26 | -------------------------------------------------------------------------------- /js/jstest/docker.go: -------------------------------------------------------------------------------- 1 | //+build !js 2 | 3 | package jstest 4 | 5 | import ( 6 | "strings" 7 | 8 | "github.com/ory/dockertest/docker" 9 | ) 10 | 11 | func pullIfNotExists(cli *docker.Client, image string) bool { 12 | _, err := cli.InspectImage(image) 13 | if err == nil { 14 | return true 15 | } 16 | i := strings.Index(image, ":") 17 | err = cli.PullImage(docker.PullImageOptions{ 18 | Repository: image[:i], Tag: image[i+1:], 19 | }, docker.AuthConfiguration{}) 20 | return err == nil 21 | } 22 | -------------------------------------------------------------------------------- /storage/json.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | var ErrNotFound = errors.New("not found") 9 | 10 | func GetItemJSON(s Storage, key string, dst interface{}) error { 11 | v, ok := s.GetItem(key) 12 | if !ok { 13 | return ErrNotFound 14 | } 15 | return json.Unmarshal([]byte(v), dst) 16 | } 17 | 18 | func SetItemJSON(s Storage, key string, val interface{}) error { 19 | data, err := json.Marshal(val) 20 | if err != nil { 21 | return err 22 | } 23 | s.SetItem(key, string(data)) 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /style.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | type Style struct { 6 | v js.Value 7 | } 8 | 9 | func AsStyle(v js.Value) *Style { 10 | if !v.Valid() { 11 | return nil 12 | } 13 | return &Style{v: v} 14 | } 15 | 16 | func (s *Style) SetWidth(v Unit) { 17 | s.v.Set("width", v.String()) 18 | } 19 | 20 | func (s *Style) SetHeight(v Unit) { 21 | s.v.Set("height", v.String()) 22 | } 23 | 24 | func (s *Style) SetMarginsRaw(m string) { 25 | s.v.Set("margin", m) 26 | } 27 | 28 | func (s *Style) Set(k string, v interface{}) { 29 | s.v.Set(k, v) 30 | } 31 | -------------------------------------------------------------------------------- /examples/grpc-over-ws/protocol/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 4 | 5 | option (gogoproto.protosizer_all) = true; 6 | option (gogoproto.sizer_all) = false; 7 | option (gogoproto.marshaler_all) = true; 8 | option (gogoproto.unmarshaler_all) = true; 9 | option (gogoproto.goproto_getters_all) = false; 10 | option go_package = "protocol"; 11 | 12 | message HelloReq { 13 | string name = 1; 14 | } 15 | 16 | message HelloResp { 17 | string text = 1; 18 | } 19 | 20 | service HelloService { 21 | rpc Hello (HelloReq) returns (HelloResp); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /storage/common.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | type Storage interface { 4 | // Length returns an integer representing the number of data items stored in the Storage object. 5 | Length() int 6 | // Key will return the name of the nth key in the storage. 7 | Key(ind int) string 8 | // GetItem will return that key's value. 9 | GetItem(key string) (string, bool) 10 | // SetItem will add that key to the storage, or update that key's value if it already exists. 11 | SetItem(key, val string) 12 | // RemoveItem will remove that key from the storage. 13 | RemoveItem(key string) 14 | // Clear will empty all keys out of the storage. 15 | Clear() 16 | } 17 | -------------------------------------------------------------------------------- /global.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/dennwc/dom/js" 7 | ) 8 | 9 | var ( 10 | Doc = GetDocument() 11 | Body = getFirstWithTag("body") 12 | Head = getFirstWithTag("head") 13 | ) 14 | 15 | func getFirstWithTag(tag string) *HTMLElement { 16 | list := Doc.GetElementsByTagName(tag) 17 | if len(list) == 0 { 18 | return nil 19 | } 20 | return list[0].AsHTMLElement() 21 | } 22 | 23 | // Value is an alias for js.Wrapper. 24 | // 25 | // Derprecated: use js.Wrapper 26 | type Value = js.Wrapper 27 | 28 | func ConsoleLog(args ...interface{}) { 29 | js.Get("console").Call("log", args...) 30 | } 31 | 32 | func Loop() { 33 | select {} 34 | } 35 | 36 | type Point = image.Point 37 | type Rect = image.Rectangle 38 | -------------------------------------------------------------------------------- /require/require_test.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package require 4 | 5 | import ( 6 | "github.com/dennwc/dom/js/jstest" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | var ( 14 | modtime = time.Now() 15 | files = map[string]string{ 16 | "env.js": `Val = 'ok'`, 17 | "err.js": `= 'ok'`, 18 | } 19 | ) 20 | 21 | func TestRequire(t *testing.T) { 22 | jstest.RunTestChrome(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | name := strings.Trim(r.URL.Path, "/") 24 | if !strings.HasSuffix(name, ".js") { 25 | name += ".js" 26 | } 27 | data, ok := files[name] 28 | if !ok { 29 | w.WriteHeader(http.StatusNotFound) 30 | return 31 | } 32 | http.ServeContent(w, r, name, modtime, strings.NewReader(data)) 33 | })) 34 | } 35 | -------------------------------------------------------------------------------- /shadowRoot.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | // https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot 6 | 7 | func AsShadowRoot(v js.Value) *ShadowRoot { 8 | if !v.Valid() { 9 | return nil 10 | } 11 | return &ShadowRoot{NodeBase{v: v}} 12 | } 13 | 14 | var _ Node = (*ShadowRoot)(nil) 15 | 16 | type ShadowRoot struct { 17 | NodeBase 18 | } 19 | 20 | func (r *ShadowRoot) IsOpen() bool { 21 | if r.v.Get("mode").String() == "open" { 22 | return true 23 | } 24 | return false 25 | } 26 | 27 | func (r *ShadowRoot) Host() *Element { 28 | return AsElement(r.v.Get("host")) 29 | } 30 | 31 | func (r *ShadowRoot) InnerHTML() string { 32 | return r.v.Get("innerHTML").String() 33 | } 34 | 35 | func (r *ShadowRoot) SetInnerHTML(s string) { 36 | r.v.Set("innerHTML", s) 37 | } 38 | -------------------------------------------------------------------------------- /net/ws/wsconn_js_test.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package ws 4 | 5 | import ( 6 | "net/rpc" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/dennwc/dom/js" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestWebSocketsJS(t *testing.T) { 15 | url := js.Get("window", "location").String() 16 | url = "ws://" + strings.TrimPrefix(url, "http://") + "/ws" 17 | c, err := Dial(url) 18 | require.NoError(t, err) 19 | defer c.Close() 20 | 21 | cli := rpc.NewClient(c) 22 | 23 | var out string 24 | err = cli.Call("S.Hello", "Alice", &out) 25 | require.NoError(t, err) 26 | require.Equal(t, "Hello Alice", out) 27 | } 28 | 29 | func TestWebSocketsJSFail(t *testing.T) { 30 | _, err := Dial("ws://localhost:80") 31 | require.NotNil(t, err) 32 | require.Equal(t, "ws.dial: connection closed with code 1006", err.Error()) 33 | } 34 | -------------------------------------------------------------------------------- /require/require_js_test.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package require 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/dennwc/dom/js" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestRequireJS(t *testing.T) { 13 | err := Require("/env.js") 14 | require.NoError(t, err) 15 | require.Equal(t, "ok", js.Get("Val").String()) 16 | } 17 | 18 | func TestRequireJSNoExt(t *testing.T) { 19 | err := Script("/env") 20 | require.NoError(t, err) 21 | require.Equal(t, "ok", js.Get("Val").String()) 22 | } 23 | 24 | func TestRequireJSSyntaxError(t *testing.T) { 25 | t.SkipNow() // FIXME 26 | err := Require("/err.js") 27 | require.NotNil(t, err) 28 | } 29 | 30 | func TestRequireJSNotFound(t *testing.T) { 31 | err := Require("/na.js") 32 | require.NotNil(t, err) 33 | } 34 | 35 | func TestRequireJSNotFoundNoExt(t *testing.T) { 36 | err := Script("/na") 37 | 38 | require.NotNil(t, err) 39 | } 40 | -------------------------------------------------------------------------------- /examples/grpc-over-ws/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | gRPC over WS 12 | 13 | 14 | 15 | 16 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/grpc-over-ws/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | 9 | "google.golang.org/grpc" 10 | 11 | "github.com/dennwc/dom/examples/grpc-over-ws/protocol" 12 | "github.com/dennwc/dom/net/ws" 13 | ) 14 | 15 | //go:generate GOOS=js GOARCH=wasm go build -o app.wasm ./client.go 16 | 17 | func main() { 18 | s := server{} 19 | 20 | srv := grpc.NewServer() 21 | protocol.RegisterService(srv, s) 22 | 23 | const host = "localhost:8080" 24 | 25 | handler := http.FileServer(http.Dir(".")) 26 | lis, err := ws.Listen("ws://"+host+"/ws", handler) 27 | if err != nil { 28 | panic(err) 29 | } 30 | defer lis.Close() 31 | 32 | log.Printf("listening on http://%s", host) 33 | err = srv.Serve(lis) 34 | if err != nil { 35 | panic(err) 36 | } 37 | } 38 | 39 | type server struct{} 40 | 41 | func (server) Hello(ctx context.Context, name string) (string, error) { 42 | return fmt.Sprintf("Hello, %s!", name), nil 43 | } 44 | -------------------------------------------------------------------------------- /net/ws/wsconn_test.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package ws 4 | 5 | import ( 6 | "github.com/dennwc/dom/js/jstest" 7 | "github.com/stretchr/testify/require" 8 | "net/http" 9 | "net/rpc" 10 | "testing" 11 | ) 12 | 13 | type service struct{} 14 | 15 | func (service) Hello(name string, out *string) error { 16 | *out = "Hello " + name 17 | return nil 18 | } 19 | 20 | func TestWebSockets(t *testing.T) { 21 | errc := make(chan error, 1) 22 | rs := rpc.NewServer() 23 | err := rs.RegisterName("S", service{}) 24 | require.NoError(t, err) 25 | 26 | srv := newServer(nil) 27 | defer srv.Close() 28 | 29 | lis := srv 30 | 31 | go func() { 32 | for { 33 | c, err := lis.Accept() 34 | if err != nil { 35 | errc <- err 36 | return 37 | } 38 | go func() { 39 | defer c.Close() 40 | rs.ServeConn(c) 41 | }() 42 | } 43 | }() 44 | 45 | jstest.RunTestChrome(t, http.HandlerFunc(srv.handleWS)) 46 | select { 47 | case err = <-errc: 48 | require.NoError(t, err) 49 | default: 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /js/js_wasm.go: -------------------------------------------------------------------------------- 1 | //+build wasm 2 | 3 | package js 4 | 5 | import ( 6 | "syscall/js" 7 | ) 8 | 9 | var ( 10 | global = js.Global() 11 | null = js.Null() 12 | undefined = js.Undefined() 13 | ) 14 | 15 | // Ref is an alias for syscall/js.Value. 16 | type Ref = js.Value 17 | 18 | // Error is an alias for syscall/js.Error. 19 | type Error = js.Error 20 | 21 | // Type is a type name of a JS value, as returned by "typeof". 22 | type Type = js.Type 23 | 24 | const ( 25 | TypeObject = js.TypeObject 26 | TypeFunction = js.TypeFunction 27 | ) 28 | 29 | // Wrapper is an alias for syscall/js.Wrapper. 30 | type Wrapper = js.Wrapper 31 | 32 | func valueOf(o interface{}) Ref { 33 | return js.ValueOf(toJS(o)) 34 | } 35 | 36 | // Func is a wrapped Go function to be called by JavaScript. 37 | type Func = js.Func 38 | 39 | func funcOf(fnc func(this Ref, refs []Ref) interface{}) Func { 40 | return js.FuncOf(fnc) 41 | } 42 | 43 | func typedArrayOf(slice interface{}) Ref { 44 | return js.TypedArrayOf(slice).Value 45 | } 46 | 47 | func releaseTypedArray(v Ref) { 48 | js.TypedArray{v}.Release() 49 | } 50 | -------------------------------------------------------------------------------- /js/json_test.go: -------------------------------------------------------------------------------- 1 | //+build wasm,js 2 | 3 | package js 4 | 5 | import ( 6 | "encoding/json" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestValueMarshalJSON(t *testing.T) { 13 | t.Run("object", func(t *testing.T) { 14 | v := NewObject() 15 | v.Set("k", "v") 16 | require.True(t, v.Valid()) 17 | 18 | data, err := v.MarshalJSON() 19 | require.NoError(t, err) 20 | require.Equal(t, `{"k":"v"}`, string(data)) 21 | }) 22 | t.Run("undefined", func(t *testing.T) { 23 | var v Value 24 | data, err := v.MarshalJSON() 25 | require.NoError(t, err) 26 | require.Equal(t, "null", string(data)) 27 | }) 28 | } 29 | 30 | func TestValueUnmarshalJSON(t *testing.T) { 31 | var v Value 32 | err := json.Unmarshal([]byte(`{"k":"v"}`), &v) 33 | require.NoError(t, err) 34 | 35 | require.Equal(t, "v", v.Get("k").String()) 36 | } 37 | 38 | func TestValueUnmarshalJSONError(t *testing.T) { 39 | var v Value 40 | // Call unmarshal directly, because json.Unmarshal will try to validate an input. 41 | err := v.UnmarshalJSON([]byte(`{"k":"v"`)) 42 | require.NotNil(t, err) 43 | } 44 | -------------------------------------------------------------------------------- /net/webrtc/signalling.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | // Signal describes a message that will be sent via signalling channel to discover peers. 4 | type Signal struct { 5 | UID string // optional user identifier to distinguish peers 6 | Data []byte // payload generated by WebRTC to establish connection 7 | } 8 | 9 | // AnswerStream is stream of responses to a discovery request. 10 | type AnswerStream interface { 11 | Next() (Signal, error) 12 | Close() error 13 | } 14 | 15 | // Offer is a discovery message from a specific peer. 16 | type Offer interface { 17 | Info() Signal 18 | Answer(s Signal) error 19 | } 20 | 21 | // OfferStream is a stream of discovery messages coming from other peers. 22 | type OfferStream interface { 23 | Next() (Offer, error) 24 | Close() error 25 | } 26 | 27 | // Signalling is an interface for a signalling connection that helps to establish P2P connections. 28 | type Signalling interface { 29 | // Broadcast sends a peer discovery request. 30 | Broadcast(s Signal) (AnswerStream, error) 31 | // Listen starts listening for incoming peer discovery requests. 32 | Listen(uid string) (OfferStream, error) 33 | } 34 | -------------------------------------------------------------------------------- /storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "github.com/dennwc/dom/js" 5 | ) 6 | 7 | func getStorage(name string) Storage { 8 | s := js.Get("window").Get(name) 9 | if s.IsNull() || s.IsUndefined() { 10 | return nil 11 | } 12 | return jsStorage{s} 13 | } 14 | 15 | func Local() Storage { 16 | return getStorage("localStorage") 17 | } 18 | 19 | func Session() Storage { 20 | return getStorage("sessionStorage") 21 | } 22 | 23 | type jsStorage struct { 24 | v js.Value 25 | } 26 | 27 | func (s jsStorage) Length() int { 28 | return s.v.Get("length").Int() 29 | } 30 | 31 | func (s jsStorage) Key(ind int) string { 32 | return s.v.Call("key", ind).String() 33 | } 34 | 35 | func (s jsStorage) GetItem(key string) (string, bool) { 36 | v := s.v.Call("getItem", key) 37 | if v.IsNull() || v.IsUndefined() { 38 | return "", false 39 | } 40 | return v.String(), true 41 | } 42 | 43 | func (s jsStorage) SetItem(key, val string) { 44 | s.v.Call("setItem", key, val) 45 | } 46 | 47 | func (s jsStorage) RemoveItem(key string) { 48 | s.v.Call("removeItem", key) 49 | } 50 | 51 | func (s jsStorage) Clear() { 52 | s.v.Call("clear") 53 | } 54 | -------------------------------------------------------------------------------- /examples/grpc-over-ws/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "context" 5 | 6 | xcontext "golang.org/x/net/context" 7 | 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | //go:generate protoc --proto_path=$GOPATH/src:. --gogo_out=plugins=grpc:. ./hello.proto 12 | 13 | type Service interface { 14 | Hello(ctx context.Context, name string) (string, error) 15 | } 16 | 17 | func AsService(cc *grpc.ClientConn) Service { 18 | cli := NewHelloServiceClient(cc) 19 | return implClient{cli} 20 | } 21 | 22 | func RegisterService(srv *grpc.Server, s Service) { 23 | RegisterHelloServiceServer(srv, implServer{s}) 24 | } 25 | 26 | type implClient struct { 27 | cli HelloServiceClient 28 | } 29 | 30 | func (c implClient) Hello(ctx context.Context, name string) (string, error) { 31 | resp, err := c.cli.Hello(ctx, &HelloReq{Name: name}) 32 | if err != nil { 33 | return "", err 34 | } 35 | return resp.Text, nil 36 | } 37 | 38 | type implServer struct { 39 | srv Service 40 | } 41 | 42 | func (s implServer) Hello(ctx xcontext.Context, req *HelloReq) (*HelloResp, error) { 43 | txt, err := s.srv.Hello(ctx, req.Name) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return &HelloResp{Text: txt}, nil 48 | } 49 | -------------------------------------------------------------------------------- /input.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | func (d *Document) NewInput(typ string) *Input { 4 | e := d.CreateElement("input") 5 | inp := &Input{HTMLElement{*e}} 6 | inp.SetType(typ) 7 | return inp 8 | } 9 | 10 | func NewInput(typ string) *Input { 11 | return Doc.NewInput(typ) 12 | } 13 | 14 | type Input struct { 15 | HTMLElement 16 | } 17 | 18 | func (inp *Input) Value() string { 19 | return inp.v.Get("value").String() 20 | } 21 | func (inp *Input) SetType(typ string) { 22 | inp.SetAttribute("type", typ) 23 | } 24 | func (inp *Input) SetName(name string) { 25 | inp.SetAttribute("name", name) 26 | } 27 | func (inp *Input) SetValue(val interface{}) { 28 | inp.v.Set("value", val) 29 | } 30 | func (inp *Input) OnChange(h EventHandler) { 31 | inp.AddEventListener("change", h) 32 | } 33 | func (inp *Input) OnInput(h EventHandler) { 34 | inp.AddEventListener("input", h) 35 | } 36 | 37 | func (d *Document) NewButton(s string) *Button { 38 | e := d.CreateElement("button") 39 | b := &Button{*e} 40 | b.SetInnerHTML(s) 41 | return b 42 | } 43 | 44 | func NewButton(s string) *Button { 45 | return Doc.NewButton(s) 46 | } 47 | 48 | type Button struct { 49 | Element 50 | } 51 | 52 | func (b *Button) OnClick(h EventHandler) { 53 | b.AddEventListener("click", h) 54 | } 55 | -------------------------------------------------------------------------------- /window.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/dennwc/dom/js" 7 | ) 8 | 9 | func GetWindow() *Window { 10 | win := js.Get("window") 11 | if !win.Valid() { 12 | return nil 13 | } 14 | return &Window{v: win} 15 | } 16 | 17 | var _ EventTarget = (*Window)(nil) 18 | 19 | type Window struct { 20 | v js.Value 21 | } 22 | 23 | func (w *Window) JSValue() js.Ref { 24 | return w.v.JSValue() 25 | } 26 | 27 | func (w *Window) AddEventListener(typ string, h EventHandler) { 28 | w.v.Call("addEventListener", typ, js.NewEventCallback(func(v js.Value) { 29 | h(convertEvent(v)) 30 | })) 31 | } 32 | 33 | func (w *Window) Open(url, windowName string, windowFeatures map[string]string) { 34 | w.v.Call("open", url, windowName, joinKeyValuePairs(windowFeatures, ",")) 35 | } 36 | 37 | func (w *Window) SetLocation(url string) { 38 | w.v.Set("location", url) 39 | } 40 | 41 | func (w *Window) OnResize(fnc func(e Event)) { 42 | w.AddEventListener("resize", fnc) 43 | } 44 | 45 | func joinKeyValuePairs(kvpair map[string]string, joiner string) string { 46 | if kvpair == nil { 47 | return "" 48 | } 49 | 50 | pairs := make([]string, 0, len(kvpair)) 51 | for k, v := range kvpair { 52 | pairs = append(pairs, k+"="+v) 53 | } 54 | return strings.Join(pairs, joiner) 55 | } 56 | -------------------------------------------------------------------------------- /units.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | var ( 8 | _ Unit = Px(0) 9 | _ Unit = Em(0) 10 | _ Unit = Rem(0) 11 | _ Unit = Vw(0) 12 | _ Unit = Vh(0) 13 | _ Unit = Vmin(0) 14 | _ Unit = Vmax(0) 15 | _ Unit = Perc(0) 16 | ) 17 | 18 | type Unit interface { 19 | String() string 20 | } 21 | 22 | type Auto struct{} 23 | 24 | func (Auto) String() string { 25 | return "auto" 26 | } 27 | 28 | type Px int 29 | 30 | func (v Px) String() string { 31 | return strconv.Itoa(int(v)) + "px" 32 | } 33 | 34 | type Em float64 35 | 36 | func (v Em) String() string { 37 | return strconv.FormatFloat(float64(v), 'g', -1, 64) + "em" 38 | } 39 | 40 | type Rem int 41 | 42 | func (v Rem) String() string { 43 | return strconv.Itoa(int(v)) + "rem" 44 | } 45 | 46 | type Vw int 47 | 48 | func (v Vw) String() string { 49 | return strconv.Itoa(int(v)) + "vw" 50 | } 51 | 52 | type Vh int 53 | 54 | func (v Vh) String() string { 55 | return strconv.Itoa(int(v)) + "vh" 56 | } 57 | 58 | type Vmin int 59 | 60 | func (v Vmin) String() string { 61 | return strconv.Itoa(int(v)) + "vmin" 62 | } 63 | 64 | type Vmax int 65 | 66 | func (v Vmax) String() string { 67 | return strconv.Itoa(int(v)) + "vmax" 68 | } 69 | 70 | type Perc int 71 | 72 | func (v Perc) String() string { 73 | return strconv.Itoa(int(v)) + "%" 74 | } 75 | -------------------------------------------------------------------------------- /examples/grpc-over-ws/client.go: -------------------------------------------------------------------------------- 1 | //+build wasm,js 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "net" 8 | "time" 9 | 10 | "google.golang.org/grpc" 11 | 12 | "github.com/dennwc/dom" 13 | "github.com/dennwc/dom/examples/grpc-over-ws/protocol" 14 | "github.com/dennwc/dom/net/ws" 15 | ) 16 | 17 | func dialer(s string, dt time.Duration) (net.Conn, error) { 18 | return ws.Dial(s) 19 | } 20 | 21 | func main() { 22 | p1 := dom.Doc.CreateElement("p") 23 | dom.Body.AppendChild(p1) 24 | 25 | inp := dom.Doc.NewInput("text") 26 | p1.AppendChild(inp) 27 | 28 | btn := dom.Doc.NewButton("Go!") 29 | p1.AppendChild(btn) 30 | 31 | ch := make(chan string, 1) 32 | btn.OnClick(func(_ dom.Event) { 33 | ch <- inp.Value() 34 | }) 35 | 36 | conn, err := grpc.Dial("ws://localhost:8080/ws", grpc.WithDialer(dialer), grpc.WithInsecure()) 37 | if err != nil { 38 | panic(err) 39 | } 40 | defer conn.Close() 41 | 42 | cli := protocol.AsService(conn) 43 | 44 | printMsg := func(s string) { 45 | p := dom.Doc.CreateElement("p") 46 | p.SetTextContent(s) 47 | dom.Body.AppendChild(p) 48 | } 49 | 50 | ctx := context.Background() 51 | for { 52 | name := <-ch 53 | printMsg("say hello to: " + name) 54 | 55 | txt, err := cli.Hello(ctx, name) 56 | if err != nil { 57 | panic(err) 58 | } 59 | printMsg(txt) 60 | } 61 | 62 | dom.Loop() 63 | } 64 | -------------------------------------------------------------------------------- /js/json.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | _ json.Marshaler = Value{} 12 | _ json.Unmarshaler = (*Value)(nil) 13 | ) 14 | 15 | var ( 16 | jsonObj Ref 17 | jsonParse Ref 18 | jsonStringify Ref 19 | jsonOnce sync.Once 20 | ) 21 | 22 | func initJSON() { 23 | jsonObj = global.Get("JSON") 24 | if jsonObj == undefined { 25 | return 26 | } 27 | jsonParse = jsonObj.Get("parse") 28 | jsonStringify = jsonObj.Get("stringify") 29 | } 30 | 31 | // MarshalJSON encodes a value into JSON by using native JavaScript function (JSON.stringify). 32 | func (v Value) MarshalJSON() ([]byte, error) { 33 | jsonOnce.Do(initJSON) 34 | if jsonStringify == undefined { 35 | return nil, errors.New("json encoding is not supported") 36 | } 37 | if v.Ref == undefined { 38 | return []byte("null"), nil 39 | } 40 | s := jsonStringify.Invoke(v.Ref).String() 41 | return []byte(s), nil 42 | } 43 | 44 | // UnmarshalJSON decodes a value from JSON by using native JavaScript functions (JSON.parse). 45 | func (v *Value) UnmarshalJSON(p []byte) (err error) { 46 | jsonOnce.Do(initJSON) 47 | if jsonParse == undefined { 48 | return errors.New("json decoding is not supported") 49 | } 50 | defer func() { 51 | if r := recover(); r != nil { 52 | if e, ok := r.(error); ok { 53 | err = e 54 | } else { 55 | err = fmt.Errorf("%v", r) 56 | } 57 | } 58 | }() 59 | v.Ref = jsonParse.Invoke(string(p)) 60 | return err 61 | } 62 | -------------------------------------------------------------------------------- /document.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | func GetDocument() *Document { 6 | doc := js.Get("document") 7 | if !doc.Valid() { 8 | return nil 9 | } 10 | return &Document{NodeBase{v: doc}} 11 | } 12 | 13 | func NewElement(tag string) *Element { 14 | return Doc.CreateElement(tag) 15 | } 16 | 17 | var _ Node = (*Document)(nil) 18 | 19 | type Document struct { 20 | NodeBase 21 | } 22 | 23 | func (d *Document) CreateElement(tag string) *Element { 24 | if d == nil { 25 | return nil 26 | } 27 | v := d.v.Call("createElement", tag) 28 | return AsElement(v) 29 | } 30 | func (d *Document) CreateElementNS(ns string, tag string) *Element { 31 | if d == nil { 32 | return nil 33 | } 34 | v := d.v.Call("createElementNS", ns, tag) 35 | return AsElement(v) 36 | } 37 | func (d *Document) GetElementById(id string) *Element { 38 | if d == nil { 39 | return nil 40 | } 41 | v := d.v.Call("getElementById", id) 42 | return AsElement(v) 43 | } 44 | func (d *Document) GetElementsByTagName(tag string) NodeList { 45 | if d == nil { 46 | return nil 47 | } 48 | v := d.v.Call("getElementsByTagName", tag) 49 | return AsNodeList(v) 50 | } 51 | func (d *Document) QuerySelector(qu string) *Element { 52 | if d == nil { 53 | return nil 54 | } 55 | v := d.v.Call("querySelector", qu) 56 | return AsElement(v) 57 | } 58 | func (d *Document) QuerySelectorAll(qu string) NodeList { 59 | if d == nil { 60 | return nil 61 | } 62 | v := d.v.Call("querySelectorAll", qu) 63 | return AsNodeList(v) 64 | } 65 | -------------------------------------------------------------------------------- /js/jstest/build.go: -------------------------------------------------------------------------------- 1 | //+build !js 2 | 3 | package jstest 4 | 5 | import ( 6 | "github.com/stretchr/testify/require" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "testing" 12 | 13 | "github.com/dennwc/dom/internal/goenv" 14 | ) 15 | 16 | const ( 17 | wasmDir = "misc/wasm" 18 | wasmExec = "go_js_wasm_exec" 19 | wasmExecJS = "wasm_exec.js" 20 | wasmExecHTML = "wasm_exec.html" 21 | ) 22 | 23 | func buildTestJS(t testing.TB) string { 24 | // compile js tests 25 | GOROOT := goenv.GOROOT() 26 | f, err := ioutil.TempFile("", "js_test_") 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | f.Close() 31 | 32 | cmd := exec.Command(goenv.Go(), "test", "-c", "-o", f.Name(), ".") 33 | cmd.Env = append(cmd.Env, os.Environ()...) 34 | cmd.Env = append(cmd.Env, []string{ 35 | "GOROOT=" + GOROOT, 36 | "GOARCH=wasm", 37 | "GOOS=js", 38 | }...) 39 | cmd.Stderr = os.Stderr 40 | err = cmd.Run() 41 | if err == nil { 42 | err = os.Chmod(f.Name(), 0755) 43 | } 44 | if err != nil { 45 | os.Remove(f.Name()) 46 | t.Fatal(err) 47 | } 48 | return f.Name() 49 | } 50 | 51 | func copyFile(t testing.TB, src, dst string) { 52 | r, err := os.Open(src) 53 | require.NoError(t, err) 54 | defer r.Close() 55 | 56 | w, err := os.Create(dst) 57 | require.NoError(t, err) 58 | defer w.Close() 59 | 60 | _, err = io.Copy(w, r) 61 | require.NoError(t, err) 62 | 63 | fi, err := r.Stat() 64 | require.NoError(t, err) 65 | 66 | err = w.Chmod(fi.Mode()) 67 | require.NoError(t, err) 68 | 69 | err = w.Close() 70 | require.NoError(t, err) 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dennwc/dom 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect 7 | github.com/Microsoft/go-winio v0.4.11 // indirect 8 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect 9 | github.com/cenkalti/backoff v2.0.0+incompatible // indirect 10 | github.com/chromedp/cdproto v0.0.0-20190217000753-2d8e8962ceb2 11 | github.com/chromedp/chromedp v0.1.3 12 | github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac // indirect 13 | github.com/dennwc/testproxy v1.0.1 14 | github.com/docker/go-connections v0.4.0 // indirect 15 | github.com/docker/go-units v0.3.3 // indirect 16 | github.com/google/go-cmp v0.2.0 // indirect 17 | github.com/gorilla/websocket v1.4.0 18 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect 19 | github.com/lib/pq v1.0.0 // indirect 20 | github.com/opencontainers/go-digest v1.0.0-rc1 // indirect 21 | github.com/opencontainers/image-spec v1.0.1 // indirect 22 | github.com/opencontainers/runc v0.1.1 // indirect 23 | github.com/ory/dockertest v3.3.2+incompatible 24 | github.com/pkg/errors v0.8.0 // indirect 25 | github.com/sirupsen/logrus v1.2.0 // indirect 26 | github.com/stretchr/testify v1.2.2 27 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect 28 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect 29 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 // indirect 30 | gotest.tools v2.2.0+incompatible // indirect 31 | ) 32 | 33 | replace github.com/chromedp/chromedp => github.com/dennwc/chromedp v0.1.3-0.20181116212057-ac5b86a6b983 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go DOM binding (and more) for WebAssembly 2 | 3 | This library provides a Go API for different Web APIs for WebAssembly target. 4 | 5 | It's in an active development, but an API will be carefully versioned to 6 | avoid breaking users. 7 | Use Go dependency management tools to lock a specific version. 8 | 9 | More information about Go's WebAssembly support can be found on [Go's WebAssembly wiki page](https://github.com/golang/go/wiki/WebAssembly). 10 | 11 | **Features:** 12 | 13 | - Better JS API (wrappers for `syscall/js`) 14 | - Basic DOM manipulation, styles, events 15 | - Input elements 16 | - SVG elements and transforms 17 | - `LocalStorage` and `SessionStorage` 18 | - Extension APIs (tested on Chrome): 19 | - Native Messaging 20 | - Bookmarks 21 | - Tabs 22 | - `net`-like library for WebSockets 23 | - Tested with gRPC 24 | - `wasm-server` for fast prototyping 25 | 26 | ## Quickstart 27 | 28 | Pull the library and install `wasm-server` (optional): 29 | 30 | ``` 31 | go get -u github.com/dennwc/dom 32 | go install github.com/dennwc/dom/cmd/wasm-server 33 | ``` 34 | 35 | Run an example app: 36 | 37 | ``` 38 | cd $GOPATH/src/github.com/dennwc/dom 39 | wasm-server 40 | ``` 41 | 42 | Check result: http://localhost:8080/ 43 | 44 | The source code is recompiled on each page refresh, so feel free to experiment! 45 | 46 | # Similar Projects 47 | 48 | - [go-js-dom](https://github.com/dominikh/go-js-dom) 49 | 50 | # Editor Configuration 51 | 52 | If you are using Visual Studio Code, you can use [workspace settings](https://code.visualstudio.com/docs/getstarted/settings#_creating-user-and-workspace-settings) to configure the environment variables for the go tools. 53 | 54 | Your settings.json file should look something like this: 55 | 56 | ``` 57 | { 58 | "go.toolsEnvVars": { "GOARCH": "wasm", "GOOS": "js" } 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /net/webrtc/active.go: -------------------------------------------------------------------------------- 1 | //+build js,wasm 2 | 3 | package webrtc 4 | 5 | import ( 6 | "encoding/json" 7 | "net" 8 | ) 9 | 10 | // answerStream is an implementation of peer discovery when broadcasting (active). 11 | type answerStream struct { 12 | self string // user id 13 | c *peerConnection 14 | 15 | local connInfo // local WebRTC info 16 | answers AnswerStream 17 | } 18 | 19 | func (p *answerStream) Close() error { 20 | p.answers.Close() 21 | if p.c != nil { 22 | return p.c.Close() 23 | } 24 | return nil 25 | } 26 | 27 | func (p *answerStream) Accept() (Peer, error) { 28 | // get the next answer, but don't use it yet 29 | ans, err := p.answers.Next() 30 | if err != nil { 31 | return nil, err 32 | } 33 | var info connInfo 34 | if err = json.Unmarshal(ans.Data, &info); err != nil { 35 | return nil, err 36 | } 37 | return &peerAnswer{s: p, uid: ans.UID, info: info}, nil 38 | } 39 | 40 | type peerAnswer struct { 41 | s *answerStream 42 | uid string 43 | info connInfo 44 | } 45 | 46 | func (p *peerAnswer) UID() string { 47 | return p.uid 48 | } 49 | 50 | func (p *peerAnswer) Dial() (net.Conn, error) { 51 | // if we are initiating a connection, we have just received an info from peer 52 | // and we are ready to apply its configuration and start dialing 53 | c := p.s.c 54 | 55 | // switch to this peer and try to dial it 56 | err := c.SetRemoteDescription(p.info.SDP) 57 | if err != nil { 58 | c.Close() 59 | return nil, err 60 | } 61 | 62 | err = c.SetICECandidates(p.info.ICEs) 63 | if err != nil { 64 | c.Close() 65 | return nil, err 66 | } 67 | // take ownership of the connection 68 | p.s.c = nil 69 | 70 | // now we should only wait for a state change to "connected" 71 | // but instead we will wait for a data stream to come online 72 | ch, err := c.WaitChannel(primaryChan) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return ch, nil 77 | } 78 | -------------------------------------------------------------------------------- /extension/chrome/bookmarks.go: -------------------------------------------------------------------------------- 1 | package chrome 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | type AllBookmarks interface { 6 | GetTree() []BookmarkNode 7 | } 8 | 9 | func Bookmarks() AllBookmarks { 10 | v := chrome.Get("bookmarks") 11 | if !v.Valid() { 12 | return nil 13 | } 14 | return jsBookmarks{v} 15 | } 16 | 17 | type BookmarkNode interface { 18 | js.Wrapper 19 | ID() string 20 | ParentID() string 21 | Index() int 22 | URL() string 23 | Title() string 24 | Children() []BookmarkNode 25 | } 26 | 27 | type jsBookmarks struct { 28 | v js.Value 29 | } 30 | 31 | func (b jsBookmarks) callAsync(name string, args ...interface{}) js.Value { 32 | ch := make(chan js.Value, 1) 33 | cb := js.NewEventCallback(func(v js.Value) { 34 | ch <- v 35 | }) 36 | defer cb.Release() 37 | args = append(args, cb) 38 | b.v.Call(name, args...) 39 | return <-ch 40 | } 41 | func (b jsBookmarks) GetTree() []BookmarkNode { 42 | arr := b.callAsync("getTree").Slice() 43 | nodes := make([]BookmarkNode, 0, len(arr)) 44 | for _, v := range arr { 45 | nodes = append(nodes, jsBookmarkNode{v}) 46 | } 47 | return nodes 48 | } 49 | 50 | type jsBookmarkNode struct { 51 | v js.Value 52 | } 53 | 54 | func (b jsBookmarkNode) JSValue() js.Ref { 55 | return b.v.JSValue() 56 | } 57 | 58 | func (b jsBookmarkNode) ID() string { 59 | return b.v.Get("id").String() 60 | } 61 | 62 | func (b jsBookmarkNode) ParentID() string { 63 | return b.v.Get("id").String() 64 | } 65 | 66 | func (b jsBookmarkNode) Index() int { 67 | return b.v.Get("index").Int() 68 | } 69 | 70 | func (b jsBookmarkNode) URL() string { 71 | return b.v.Get("url").String() 72 | } 73 | 74 | func (b jsBookmarkNode) Title() string { 75 | return b.v.Get("title").String() 76 | } 77 | 78 | func (b jsBookmarkNode) Children() []BookmarkNode { 79 | arr := b.v.Get("children").Slice() 80 | nodes := make([]BookmarkNode, 0, len(arr)) 81 | for _, v := range arr { 82 | nodes = append(nodes, jsBookmarkNode{v}) 83 | } 84 | return nodes 85 | } 86 | -------------------------------------------------------------------------------- /js/bytes.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var _ Wrapper = TypedArray{} 8 | 9 | // TypedArray represents a JavaScript typed array. 10 | type TypedArray struct { 11 | Value 12 | } 13 | 14 | // Release frees up resources allocated for the typed array. 15 | // The typed array and its buffer must not be accessed after calling Release. 16 | func (v TypedArray) Release() { 17 | releaseTypedArray(v.Ref) 18 | } 19 | 20 | // TypedArrayOf returns a JavaScript typed array backed by the slice's underlying array. 21 | // 22 | // The supported types are []int8, []int16, []int32, []uint8, []uint16, []uint32, []float32 and []float64. 23 | // Passing an unsupported value causes a panic. 24 | // 25 | // TypedArray.Release must be called to free up resources when the typed array will not be used any more. 26 | func TypedArrayOf(o interface{}) TypedArray { 27 | v := typedArrayOf(toJS(o)) 28 | return TypedArray{Value{v}} 29 | } 30 | 31 | var _ Wrapper = (*Memory)(nil) 32 | 33 | type Memory struct { 34 | v TypedArray 35 | p []byte 36 | } 37 | 38 | func (m *Memory) Bytes() []byte { 39 | return m.p 40 | } 41 | 42 | // CopyFrom copies binary data from JS object into Go buffer. 43 | func (m *Memory) CopyFrom(v Wrapper) error { 44 | var src Value 45 | switch v := v.(type) { 46 | case Value: 47 | src = v 48 | case TypedArray: 49 | src = v.Value 50 | default: 51 | src = Value{v.JSValue()} 52 | } 53 | 54 | switch { 55 | case src.InstanceOfClass("Uint8Array"): 56 | m.v.Call("set", src) 57 | return nil 58 | case src.InstanceOfClass("Blob"): 59 | r := New("FileReader") 60 | 61 | cg := r.NewFuncGroup() 62 | defer cg.Release() 63 | done := cg.OneTimeTrigger("loadend") 64 | errc := cg.ErrorEventChan() 65 | 66 | r.Call("readAsArrayBuffer", src) 67 | select { 68 | case err := <-errc: 69 | return err 70 | case <-done: 71 | } 72 | cg.Release() 73 | arr := New("Uint8Array", r.Get("result")) 74 | return m.CopyFrom(arr) 75 | default: 76 | return fmt.Errorf("unsupported source type") 77 | } 78 | } 79 | 80 | func (m *Memory) JSValue() Ref { 81 | return m.v.JSValue() 82 | } 83 | 84 | func (m *Memory) Release() { 85 | m.v.Release() 86 | } 87 | 88 | // MMap exposes memory of p to JS. 89 | // 90 | // Release must be called to free up resources when the memory will not be used any more. 91 | func MMap(p []byte) *Memory { 92 | v := TypedArrayOf(p) 93 | return &Memory{p: p, v: v} 94 | } 95 | -------------------------------------------------------------------------------- /js/promise.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | var ( 8 | _ Wrapper = (*Promise)(nil) 9 | ) 10 | 11 | // NewPromise runs a given function asynchronously by converting it to JavaScript promise. 12 | // Promise will be resolved if functions returns a nil error and will be rejected otherwise. 13 | func NewPromise(fnc func() ([]interface{}, error)) Value { 14 | var initFunc Func 15 | initFunc = AsyncCallbackOf(func(args []Value) { 16 | initFunc.Release() 17 | resolve, reject := args[0], args[1] 18 | res, err := fnc() 19 | if err != nil { 20 | if w, ok := err.(Wrapper); ok { 21 | reject.Invoke(w) 22 | } else { 23 | reject.Invoke(err.Error()) 24 | } 25 | } else { 26 | resolve.Invoke(res...) 27 | } 28 | }) 29 | return New("Promise", initFunc) 30 | } 31 | 32 | // Promise represents a JavaScript Promise. 33 | type Promise struct { 34 | v Value 35 | done <-chan struct{} 36 | res []Value 37 | err error 38 | } 39 | 40 | // JSValue implements Wrapper interface. 41 | func (p *Promise) JSValue() Ref { 42 | return p.v.JSValue() 43 | } 44 | 45 | // Await for the promise to resolve. 46 | func (p *Promise) Await() ([]Value, error) { 47 | <-p.done 48 | return p.res, p.err 49 | } 50 | 51 | // AwaitContext for the promise to resolve or context to be canceled. 52 | func (p *Promise) AwaitContext(ctx context.Context) ([]Value, error) { 53 | select { 54 | case <-ctx.Done(): 55 | return nil, ctx.Err() 56 | case <-p.done: 57 | return p.res, p.err 58 | } 59 | } 60 | 61 | // Promised returns converts the value into a Promise. 62 | func (v Value) Promised() *Promise { 63 | done := make(chan struct{}) 64 | p := &Promise{ 65 | v: v, done: done, 66 | } 67 | var then, catch Func 68 | then = CallbackOf(func(v []Value) { 69 | then.Release() 70 | catch.Release() 71 | p.res = v 72 | close(done) 73 | }) 74 | catch = CallbackOf(func(v []Value) { 75 | then.Release() 76 | catch.Release() 77 | var e Value 78 | if len(v) != 0 { 79 | e = v[0] 80 | } 81 | if e.Ref == undefined { 82 | e = NewObject() 83 | } 84 | p.err = NewError(e) 85 | close(done) 86 | }) 87 | v.Call("then", then).Call("catch", catch) 88 | return p 89 | } 90 | 91 | // Await wait for the promise to be resolved or rejected. 92 | // A shorthand for calling Await on the promise returned by Promised. 93 | func (v Value) Await() ([]Value, error) { 94 | return v.Promised().Await() 95 | } 96 | -------------------------------------------------------------------------------- /net/webrtc/webrtc.go: -------------------------------------------------------------------------------- 1 | //+build js,wasm 2 | 3 | package webrtc 4 | 5 | import ( 6 | "encoding/json" 7 | "net" 8 | 9 | "github.com/dennwc/dom/js" 10 | ) 11 | 12 | // Peers represents a dynamic list of peers that were discovered via signalling. 13 | type Peers interface { 14 | // Accept queries an information about next available peer. It won't connect to it automatically. 15 | Accept() (Peer, error) 16 | // Close ends a peer discovery process. 17 | Close() error 18 | } 19 | 20 | // Peer represents an information about a potential peer. 21 | type Peer interface { 22 | // UID returns a optional user ID of this peer. 23 | UID() string 24 | // Dial establishes a new connection to this peer. 25 | Dial() (net.Conn, error) 26 | } 27 | 28 | // New creates a new local peer with a given ID that will use a specific server for peer discovery. 29 | func New(uid string, s Signalling) *Local { 30 | return &Local{ 31 | uid: uid, s: s, 32 | } 33 | } 34 | 35 | // Local represents an information about local peer. 36 | type Local struct { 37 | uid string 38 | s Signalling 39 | } 40 | 41 | // Listen starts a passive peer discovery process by waiting for incoming discovery requests. 42 | func (l *Local) Listen() (Peers, error) { 43 | offers, err := l.s.Listen(l.uid) 44 | if err != nil { 45 | return nil, err 46 | } 47 | c := newPeerConnection() 48 | return &offerStream{self: l.uid, c: c, offers: offers}, nil 49 | } 50 | 51 | // Discover starts an active peer discovery process by broadcasting a discovery request. 52 | func (l *Local) Discover() (Peers, error) { 53 | c := newPeerConnection() 54 | c.NewDataChannel(primaryChan) 55 | // prepare to collect local ICEs 56 | collectICEs := c.CollectICEs() 57 | 58 | // create an offer and activate it 59 | offer, err := c.CreateOffer() 60 | if err != nil { 61 | c.Close() 62 | return nil, err 63 | } 64 | err = c.SetLocalDescription(offer) 65 | if err != nil { 66 | c.Close() 67 | return nil, err 68 | } 69 | // collect all local ICE candidates 70 | ices, err := collectICEs() 71 | if err != nil { 72 | c.Close() 73 | return nil, err 74 | } 75 | local := connInfo{ 76 | SDP: offer, ICEs: ices, 77 | } 78 | // encode and broadcast 79 | data, err := json.Marshal(local) 80 | if err != nil { 81 | c.Close() 82 | return nil, err 83 | } 84 | answers, err := l.s.Broadcast(Signal{UID: l.uid, Data: data}) 85 | if err != nil { 86 | c.Close() 87 | return nil, err 88 | } 89 | return &answerStream{self: l.uid, c: c, answers: answers, local: local}, nil 90 | } 91 | 92 | const primaryChan = "primary" 93 | 94 | // connInfo combines SDP and ICE data of a specific peer. 95 | type connInfo struct { 96 | SDP js.Value `json:"sdp"` 97 | ICEs []js.Value `json:"ices"` 98 | } 99 | -------------------------------------------------------------------------------- /js/promise_test.go: -------------------------------------------------------------------------------- 1 | //+build wasm,js 2 | 3 | package js 4 | 5 | import ( 6 | "errors" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestPromiseAwait(t *testing.T) { 14 | f1 := NativeFuncOf(`return new Promise((resolve) => { 15 | setTimeout(resolve, 0) 16 | })`) 17 | 18 | ch := make(chan []Value, 1) 19 | errc := make(chan error, 1) 20 | p := f1.Invoke().Promised() 21 | go func() { 22 | r, err := p.Await() 23 | if err != nil { 24 | errc <- err 25 | } else { 26 | ch <- r 27 | } 28 | }() 29 | 30 | select { 31 | case <-ch: 32 | case err := <-errc: 33 | t.Fatal(err) 34 | case <-time.After(time.Second): 35 | t.Fatal("deadlock") 36 | } 37 | } 38 | 39 | func TestPromiseAwaitReject(t *testing.T) { 40 | f1 := NativeFuncOf(`return new Promise((resolve, reject) => { 41 | setTimeout(reject, 0) 42 | })`) 43 | 44 | ch := make(chan []Value, 1) 45 | errc := make(chan error, 1) 46 | p := f1.Invoke().Promised() 47 | go func() { 48 | r, err := p.Await() 49 | if err != nil { 50 | errc <- err 51 | } else { 52 | ch <- r 53 | } 54 | }() 55 | 56 | select { 57 | case <-ch: 58 | t.Fatal("expected promise to be rejected") 59 | case err := <-errc: 60 | require.NotNil(t, err) 61 | case <-time.After(time.Second): 62 | t.Fatal("deadlock") 63 | } 64 | } 65 | 66 | func TestNewPromiseResolve(t *testing.T) { 67 | called := make(chan struct{}) 68 | v := NewPromise(func() ([]interface{}, error) { 69 | close(called) 70 | return nil, nil 71 | }) 72 | 73 | ch := make(chan []Value, 1) 74 | errc := make(chan error, 1) 75 | p := v.Promised() 76 | go func() { 77 | r, err := p.Await() 78 | if err != nil { 79 | errc <- err 80 | } else { 81 | ch <- r 82 | } 83 | }() 84 | 85 | select { 86 | case <-ch: 87 | select { 88 | case <-called: 89 | default: 90 | t.Fatal("function was not called") 91 | } 92 | case err := <-errc: 93 | t.Fatal(err) 94 | case <-time.After(time.Second): 95 | t.Fatal("deadlock") 96 | } 97 | } 98 | 99 | func TestNewPromiseReject(t *testing.T) { 100 | called := make(chan struct{}) 101 | v := NewPromise(func() ([]interface{}, error) { 102 | close(called) 103 | return nil, errors.New("err") 104 | }) 105 | 106 | ch := make(chan []Value, 1) 107 | errc := make(chan error, 1) 108 | p := v.Promised() 109 | go func() { 110 | r, err := p.Await() 111 | if err != nil { 112 | errc <- err 113 | } else { 114 | ch <- r 115 | } 116 | }() 117 | 118 | select { 119 | case <-ch: 120 | t.Fatal("expected promise to be rejected") 121 | case err := <-errc: 122 | require.NotNil(t, err) 123 | select { 124 | case <-called: 125 | default: 126 | t.Fatal("function was not called") 127 | } 128 | case <-time.After(time.Second): 129 | t.Fatal("deadlock") 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /net/webrtc/peer_chan.go: -------------------------------------------------------------------------------- 1 | //+build js,wasm 2 | 3 | package webrtc 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "net" 9 | "sync" 10 | "time" 11 | 12 | "github.com/dennwc/dom/js" 13 | ) 14 | 15 | type peerChannel struct { 16 | c *peerConnection 17 | name string 18 | v js.Value 19 | 20 | ready chan struct{} 21 | done chan struct{} 22 | read chan struct{} 23 | 24 | mu sync.Mutex 25 | err error 26 | rbuf bytes.Buffer 27 | } 28 | 29 | func (c *peerChannel) wakeReaders() { 30 | select { 31 | case c.read <- struct{}{}: 32 | default: 33 | } 34 | } 35 | 36 | func (c *peerChannel) handleEvent(e chanEvent) { 37 | switch e.Type { 38 | case eventError: 39 | c.mu.Lock() 40 | c.err = js.Error{Value: e.Data} 41 | c.mu.Unlock() 42 | c.Close() 43 | case eventClosed: 44 | c.mu.Lock() 45 | c.err = io.EOF 46 | c.mu.Unlock() 47 | c.Close() 48 | case eventOpened: 49 | close(c.ready) 50 | case eventMessage: 51 | c.mu.Lock() 52 | c.rbuf.WriteString(e.Data.String()) 53 | c.mu.Unlock() 54 | c.wakeReaders() 55 | } 56 | } 57 | 58 | func (c *peerChannel) Read(b []byte) (int, error) { 59 | for { 60 | var ( 61 | n int 62 | err error 63 | ) 64 | c.mu.Lock() 65 | if c.rbuf.Len() != 0 { 66 | n, err = c.rbuf.Read(b) 67 | } else { 68 | err = c.err 69 | } 70 | c.mu.Unlock() 71 | if err != nil || n != 0 { 72 | return n, err 73 | } 74 | select { 75 | case <-c.done: 76 | return 0, io.EOF 77 | case <-c.read: 78 | } 79 | } 80 | } 81 | 82 | func (c *peerChannel) Write(b []byte) (int, error) { 83 | c.mu.Lock() 84 | err := c.err 85 | c.mu.Unlock() 86 | if err != nil { 87 | return 0, err 88 | } 89 | // TODO: check if we can send byte arrays 90 | c.v.Call("send", string(b)) 91 | return len(b), nil 92 | } 93 | 94 | func (c *peerChannel) LocalAddr() net.Addr { 95 | // TODO: pretty sure it's possible to get an address 96 | return peerAddr{} 97 | } 98 | 99 | func (c *peerChannel) RemoteAddr() net.Addr { 100 | // TODO: pretty sure it's possible to get an address 101 | return peerAddr{} 102 | } 103 | 104 | func (c *peerChannel) Close() error { 105 | select { 106 | case <-c.done: 107 | default: 108 | close(c.done) 109 | } 110 | return c.c.Close() // TODO: close only current channel 111 | } 112 | 113 | func (c *peerChannel) SetDeadline(t time.Time) error { 114 | // TODO 115 | return nil 116 | } 117 | 118 | func (c *peerChannel) SetReadDeadline(t time.Time) error { 119 | // TODO 120 | return nil 121 | } 122 | 123 | func (c *peerChannel) SetWriteDeadline(t time.Time) error { 124 | // TODO 125 | return nil 126 | } 127 | 128 | var _ net.Addr = peerAddr{} 129 | 130 | type peerAddr struct{} 131 | 132 | func (peerAddr) Network() string { 133 | return "webrtc" 134 | } 135 | 136 | func (peerAddr) String() string { 137 | return "webrtc://localhost" 138 | } 139 | -------------------------------------------------------------------------------- /extension/chrome/native/native_host.go: -------------------------------------------------------------------------------- 1 | //+build !wasm 2 | 3 | // Package native provides an API for Native Messaging for Chrome extensions. 4 | // 5 | // See https://developer.chrome.com/apps/nativeMessaging for more details. 6 | package native 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "encoding/json" 12 | "io" 13 | "os" 14 | "sync" 15 | ) 16 | 17 | var ( 18 | rmu sync.Mutex 19 | ) 20 | 21 | // Recv receives a message and decodes it into dst. 22 | func Recv(dst interface{}) error { 23 | rmu.Lock() 24 | defer rmu.Unlock() 25 | var r io.Reader = os.Stdin 26 | var b [4]byte 27 | _, err := io.ReadFull(r, b[:]) 28 | if err != nil { 29 | return err 30 | } 31 | size := binary.LittleEndian.Uint32(b[:]) 32 | r = io.LimitReader(r, int64(size)) 33 | return json.NewDecoder(r).Decode(dst) 34 | } 35 | 36 | var ( 37 | wmu sync.Mutex 38 | buf = new(bytes.Buffer) 39 | ) 40 | 41 | // Send sends a message. 42 | func Send(obj interface{}) error { 43 | wmu.Lock() 44 | defer wmu.Unlock() 45 | buf.Reset() 46 | buf.Write([]byte{0, 0, 0, 0}) 47 | err := json.NewEncoder(buf).Encode(obj) 48 | if err != nil { 49 | return err 50 | } 51 | data := buf.Bytes() 52 | binary.LittleEndian.PutUint32(data[:4], uint32(buf.Len()-4)) 53 | _, err = os.Stdout.Write(data) 54 | return err 55 | } 56 | 57 | // RecvBinary receives a single binary message. 58 | // 59 | // The format of the binary messages is not defined in any spec, 60 | // thus a custom message format is used to transfer binary data. 61 | func RecvBinary() ([]byte, error) { 62 | var m struct { 63 | Data []byte `json:"d"` 64 | } 65 | err := Recv(&m) 66 | return m.Data, err 67 | } 68 | 69 | // SendBinary sends a single binary message. 70 | // 71 | // The format of the binary messages is not defined in any spec, 72 | // thus a custom message format is used to transfer binary data. 73 | func SendBinary(p []byte) error { 74 | return Send(map[string][]byte{ 75 | "d": p, 76 | }) 77 | } 78 | 79 | var accepted bool 80 | 81 | // Accept accepts a single client connection. 82 | // 83 | // Notes that communication is done via stdio/stdout, thus an application should not 84 | // read/write any data to/from these streams. 85 | func Accept() io.ReadWriter { 86 | if accepted { 87 | panic("accept can only be called once") 88 | } 89 | accepted = true 90 | return &conn{} 91 | } 92 | 93 | type conn struct { 94 | rbuf bytes.Buffer 95 | } 96 | 97 | func (conn) Write(p []byte) (int, error) { 98 | err := SendBinary(p) 99 | if err != nil { 100 | return 0, err 101 | } 102 | return len(p), nil 103 | } 104 | 105 | func (c *conn) Read(p []byte) (int, error) { 106 | if c.rbuf.Len() != 0 { 107 | return c.rbuf.Read(p) 108 | } 109 | data, err := RecvBinary() 110 | if err != nil { 111 | return 0, err 112 | } 113 | n := copy(p, data) 114 | c.rbuf.Write(data[n:]) 115 | return n, nil 116 | } 117 | -------------------------------------------------------------------------------- /net/webrtc/passive.go: -------------------------------------------------------------------------------- 1 | //+build js,wasm 2 | 3 | package webrtc 4 | 5 | import ( 6 | "encoding/json" 7 | "net" 8 | ) 9 | 10 | // offerStream is an implementation of peer discovery when listening (passive). 11 | type offerStream struct { 12 | self string // user id 13 | c *peerConnection 14 | 15 | local connInfo 16 | offers OfferStream 17 | } 18 | 19 | func (p *offerStream) Close() error { 20 | p.offers.Close() 21 | if p.c != nil { 22 | return p.c.Close() 23 | } 24 | return nil 25 | } 26 | 27 | func (p *offerStream) Accept() (Peer, error) { 28 | // we are listening for connections, so we take a next offer and present it to user to decide if he wants to dial 29 | offer, err := p.offers.Next() 30 | if err != nil { 31 | return nil, err 32 | } 33 | s := offer.Info() 34 | var info connInfo 35 | if err = json.Unmarshal(s.Data, &info); err != nil { 36 | return nil, err 37 | } 38 | return &peerOffer{s: p, uid: s.UID, info: info, offer: offer}, nil 39 | } 40 | 41 | type peerOffer struct { 42 | s *offerStream 43 | uid string 44 | info connInfo 45 | offer Offer 46 | } 47 | 48 | func (p *peerOffer) UID() string { 49 | return p.uid 50 | } 51 | 52 | func (p *peerOffer) Dial() (net.Conn, error) { 53 | // we are listening for connections, so we need to collect our local ICEs 54 | // and send an answer with our info 55 | c := p.s.c 56 | 57 | // prepare to collect local ICEs 58 | collectICEs := c.CollectICEs() 59 | 60 | // switch to this peer and start dialing it (he might reject) 61 | err := c.SetRemoteDescription(p.info.SDP) 62 | if err != nil { 63 | c.Close() 64 | return nil, err 65 | } 66 | 67 | // set remote candidates 68 | err = c.SetICECandidates(p.info.ICEs) 69 | if err != nil { 70 | c.Close() 71 | return nil, err 72 | } 73 | 74 | // we are ready to answer 75 | answer, err := c.CreateAnswer() 76 | if err != nil { 77 | c.Close() 78 | return nil, err 79 | } 80 | 81 | // switch to the config that we propose 82 | err = c.SetLocalDescription(answer) 83 | if err != nil { 84 | c.Close() 85 | return nil, err 86 | } 87 | 88 | // this allows us to gather local ICEs 89 | ices, err := collectICEs() 90 | if err != nil { 91 | c.Close() 92 | return nil, err 93 | } 94 | 95 | // now we know our own parameters 96 | local := connInfo{ 97 | SDP: answer, ICEs: ices, 98 | } 99 | 100 | // send our information to the peer 101 | data, err := json.Marshal(local) 102 | if err != nil { 103 | return nil, err 104 | } 105 | err = p.offer.Answer(Signal{UID: p.s.self, Data: data}) 106 | if err != nil { 107 | return nil, err 108 | } 109 | // take ownership of the connection 110 | p.s.c = nil 111 | 112 | // now we should only wait for a state change to "connected" 113 | // but instead we will wait for a data stream to come online 114 | ch, err := c.WaitChannel(primaryChan) 115 | if err != nil { 116 | return nil, err 117 | } 118 | return ch, nil 119 | } 120 | -------------------------------------------------------------------------------- /cmd/app/main.go: -------------------------------------------------------------------------------- 1 | // +build wasm 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/dennwc/dom" 14 | "github.com/dennwc/dom/storage" 15 | "github.com/dennwc/dom/svg" 16 | ) 17 | 18 | func init() { 19 | dom.Body.Style().SetMarginsRaw("0") 20 | } 21 | 22 | func main() { 23 | fmt.Println("running") 24 | 25 | handler := func(e dom.Event) { 26 | dom.ConsoleLog(e) 27 | fmt.Printf("event: %T %v\n", e, e.JSValue()) 28 | } 29 | 30 | inp := dom.Doc.NewInput("text") 31 | dom.Body.AppendChild(inp) 32 | inp.OnChange(handler) 33 | 34 | btn := dom.Doc.NewButton("Add") 35 | dom.Body.AppendChild(btn) 36 | 37 | const ( 38 | w, h = 300, 300 39 | pad = 12 40 | ) 41 | root := svg.NewFullscreen() 42 | 43 | center := root.NewG() 44 | center.Translate(w/2, h/2) 45 | center.NewCircle(10) 46 | 47 | type Sat struct { 48 | G *svg.G 49 | HPeriod float64 50 | } 51 | type JSat struct { 52 | R float64 `json:"r"` 53 | Orb float64 `json:"orb"` 54 | HPer float64 `json:"hper"` 55 | Text string `json:"text"` 56 | } 57 | 58 | const ( 59 | storPrefix = "go-wasm-example-" 60 | ) 61 | 62 | var ( 63 | stor = storage.Local() 64 | mu sync.Mutex 65 | sats []Sat 66 | ) 67 | 68 | addSatRaw := func(r, orb, hper float64, s string) { 69 | g := center.NewG() 70 | g.NewCircle(int(r)).Translate(orb, 0) 71 | g.NewLine().SetPos(dom.Point{0, 0}, dom.Point{int(orb), 0}) 72 | if s != "" { 73 | g.NewText(s).Translate(orb+pad, 0) 74 | } 75 | mu.Lock() 76 | sats = append(sats, Sat{G: g, HPeriod: hper}) 77 | mu.Unlock() 78 | } 79 | addSat := func(r, orb, hper float64, s string) { 80 | if stor != nil { 81 | sat := JSat{R: r, Orb: orb, HPer: hper, Text: s} 82 | id := storPrefix + strconv.Itoa(stor.Length()+1) 83 | if err := storage.SetItemJSON(stor, id, sat); err != nil { 84 | panic(err) 85 | } 86 | } 87 | addSatRaw(r, orb, hper, s) 88 | } 89 | 90 | btn.OnClick(func(_ dom.Event) { 91 | txt := inp.Value() 92 | r := 50 + rand.Float64()*75 93 | hper := 0.1 + rand.Float64()*3 94 | addSat(7, r, hper, txt) 95 | }) 96 | 97 | if stor != nil { 98 | for i := 0; i < stor.Length(); i++ { 99 | key := stor.Key(i) 100 | if !strings.HasPrefix(key, storPrefix) { 101 | continue 102 | } 103 | var s JSat 104 | if err := storage.GetItemJSON(stor, key, &s); err != nil { 105 | panic(err) 106 | } 107 | addSatRaw(s.R, s.Orb, s.HPer, s.Text) 108 | } 109 | } 110 | 111 | if len(sats) == 0 { 112 | addSat(5, 27, 1.5, "A") 113 | addSat(5, 40, 2.5, "B") 114 | } 115 | 116 | start := time.Now() 117 | const interval = time.Millisecond * 30 118 | for { 119 | dt := time.Since(start).Seconds() 120 | tr := dt * 180 121 | 122 | for _, s := range sats { 123 | t := tr / s.HPeriod 124 | t -= float64(360 * int(t/360)) 125 | s.G.Transform(svg.Rotate{A: t}) 126 | } 127 | 128 | time.Sleep(interval) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /require/require.go: -------------------------------------------------------------------------------- 1 | package require 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/dennwc/dom" 9 | "github.com/dennwc/dom/js" 10 | ) 11 | 12 | var required = make(map[string]error) 13 | 14 | func appendAndWait(e *dom.Element) error { 15 | errc := make(chan error, 1) 16 | e.AddEventListener("error", func(e dom.Event) { 17 | errc <- js.NewError(e) 18 | }) 19 | done := make(chan struct{}) 20 | e.AddEventListener("load", func(e dom.Event) { 21 | close(done) // TODO: load may happen before an error 22 | }) 23 | dom.Head.AppendChild(e) 24 | select { 25 | case err := <-errc: 26 | return err 27 | case <-done: 28 | } 29 | return nil 30 | } 31 | 32 | // Require adds a specified file (js or css) into the document and waits for it to load. 33 | // 34 | // The function relies on a file extension to detect the type. If there is no extension in 35 | // the file path, use specific function like Stylesheet or Script. As an alternative, 36 | // append a '#.js' or '#.css' suffix to a file path. 37 | func Require(path string) error { 38 | if strings.HasSuffix(path, ".css") { 39 | return Stylesheet(path) 40 | } else if strings.HasSuffix(path, ".js") { 41 | return Script(path) 42 | } 43 | return errors.New("the file should have an extension specified (or '#.ext')") 44 | } 45 | 46 | // Stylesheet add a specified CSS file into the document and waits for it to load. 47 | func Stylesheet(path string) error { 48 | if err, ok := required[path]; ok { 49 | return err 50 | } 51 | s := dom.NewElement("link") 52 | v := s.JSValue() 53 | v.Set("type", "text/css") 54 | v.Set("rel", "stylesheet") 55 | v.Set("href", path) 56 | err := appendAndWait(s) 57 | required[path] = err 58 | return err 59 | } 60 | 61 | // Script adds a specified JS file into the document and waits for it to load. 62 | func Script(path string) error { 63 | if err, ok := required[path]; ok { 64 | return err 65 | } 66 | s := dom.NewElement("script") 67 | v := s.JSValue() 68 | v.Set("async", true) 69 | v.Set("src", path) 70 | err := appendAndWait(s) 71 | required[path] = err 72 | return err 73 | } 74 | 75 | // MustRequire is the same as Require, but panics on an error. 76 | func MustRequire(path string) { 77 | err := Require(path) 78 | if err != nil { 79 | panic(err) 80 | } 81 | } 82 | 83 | // MustRequireValue loads a specified file and returns a global value with a given name. 84 | func MustRequireValue(name, path string) js.Value { 85 | MustRequire(path) 86 | return js.Get(name) 87 | } 88 | 89 | // RequireLazy is the same as Require, but returns a function that will load the file on the first call. 90 | func RequireLazy(path string) func() error { 91 | var ( 92 | once sync.Once 93 | err error 94 | ) 95 | return func() error { 96 | once.Do(func() { 97 | err = Require(path) 98 | }) 99 | return err 100 | } 101 | } 102 | 103 | // StylesheetString loads a CSS stylesheet string into the DOM. 104 | func StylesheetString(data string) { 105 | s := dom.NewElement("style") 106 | s.JSValue().Set("type", "text/css") 107 | s.SetInnerHTML(data) 108 | err := appendAndWait(s) 109 | if err != nil { 110 | panic(err) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /js/jstest/nodejs.go: -------------------------------------------------------------------------------- 1 | //+build !js 2 | 3 | package jstest 4 | 5 | import ( 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "testing" 12 | "time" 13 | 14 | "github.com/dennwc/testproxy" 15 | "github.com/ory/dockertest" 16 | "github.com/ory/dockertest/docker" 17 | 18 | "github.com/dennwc/dom/internal/goenv" 19 | "github.com/stretchr/testify/require" 20 | ) 21 | 22 | const nodejsImage = "node:8-slim" 23 | 24 | // RunTestNodeJS compiles all tests in the current working directory for WASM+JS, and runs them either in Docker or 25 | // locally with NodeJS. It will stream test results back to t. 26 | // 27 | // The caller should specify the "!js" build tag, while all JS tests in the package should include "js" build tag. 28 | func RunTestNodeJS(t *testing.T) { 29 | GOROOT := goenv.GOROOT() 30 | testFile := buildTestJS(t) 31 | defer os.Remove(testFile) 32 | 33 | if runInDocker(t, testFile, GOROOT) { 34 | return 35 | } 36 | t.Log("running locally") 37 | 38 | // run tests with Node.js 39 | if _, err := exec.LookPath("node"); err != nil { 40 | t.Skipf("cannot find NodeJS: %v", err) 41 | } 42 | 43 | wd, err := os.Getwd() 44 | require.NoError(t, err) 45 | cmd := exec.Command(filepath.Join(GOROOT, wasmDir, wasmExec), testFile) 46 | cmd.Dir = wd 47 | testproxy.RunTestBinary(t, cmd) 48 | } 49 | 50 | type dockerRunner struct { 51 | cli *docker.Client 52 | c *docker.Container 53 | } 54 | 55 | func (r dockerRunner) RunAndWait(stdout, stderr io.Writer) error { 56 | cw, err := r.cli.AttachToContainerNonBlocking(docker.AttachToContainerOptions{ 57 | Container: r.c.ID, 58 | OutputStream: stdout, ErrorStream: stderr, 59 | Stdout: true, Stderr: true, 60 | Logs: true, Stream: true, 61 | }) 62 | if err != nil { 63 | return err 64 | } 65 | defer cw.Close() 66 | 67 | err = r.cli.StartContainer(r.c.ID, nil) 68 | if err != nil { 69 | return err 70 | } 71 | return cw.Wait() 72 | } 73 | 74 | func runInDocker(t *testing.T, fname, goroot string) bool { 75 | dir, err := ioutil.TempDir("", "js_test_") 76 | require.NoError(t, err) 77 | defer os.RemoveAll(dir) 78 | 79 | copyFile(t, fname, filepath.Join(dir, "test")) 80 | for _, name := range []string{ 81 | wasmExec, wasmExecJS, wasmExecHTML, 82 | } { 83 | copyFile(t, filepath.Join(goroot, wasmDir, name), filepath.Join(dir, name)) 84 | } 85 | 86 | p, err := dockertest.NewPool("") 87 | if err != nil { 88 | return false 89 | } 90 | cli := p.Client 91 | 92 | now := time.Now() 93 | if !pullIfNotExists(cli, nodejsImage) { 94 | return false 95 | } 96 | t.Logf("pulled image %q in %v", nodejsImage, time.Since(now)) 97 | 98 | c, err := cli.CreateContainer(docker.CreateContainerOptions{ 99 | Config: &docker.Config{ 100 | Image: nodejsImage, 101 | WorkingDir: "/test", 102 | Entrypoint: []string{"./" + wasmExec, "./test", "-test.v"}, 103 | }, 104 | HostConfig: &docker.HostConfig{ 105 | Binds: []string{ 106 | dir + ":" + "/test", 107 | }, 108 | }, 109 | }) 110 | require.NoError(t, err) 111 | defer cli.RemoveContainer(docker.RemoveContainerOptions{ 112 | ID: c.ID, RemoveVolumes: true, Force: true, 113 | }) 114 | t.Log("running in Docker") 115 | 116 | testproxy.RunAndReplay(t, dockerRunner{ 117 | cli: cli, c: c, 118 | }) 119 | return true 120 | } 121 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | type Node interface { 6 | EventTarget 7 | 8 | // properties 9 | 10 | BaseURI() string 11 | NodeName() string 12 | ChildNodes() NodeList 13 | ParentNode() Node 14 | ParentElement() *Element 15 | TextContent() string 16 | SetTextContent(s string) 17 | 18 | // methods 19 | 20 | AppendChild(n Node) 21 | Contains(n Node) bool 22 | IsEqualNode(n Node) bool 23 | IsSameNode(n Node) bool 24 | RemoveChild(n Node) Node 25 | ReplaceChild(n, old Node) Node 26 | } 27 | 28 | type NodeType int 29 | 30 | //See https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#Node_type_constants 31 | const ( 32 | ElementNode NodeType = 1 33 | TextNode NodeType = 3 34 | CDataSectionNode NodeType = 4 35 | ProcessingInstructionNode NodeType = 7 36 | CommentNode NodeType = 8 37 | DocumentNode NodeType = 9 38 | DocumentTypeNode NodeType = 10 39 | DocumentFragmentNode NodeType = 11 40 | ) 41 | 42 | var _ js.Wrapper = NodeBase{} 43 | 44 | type NodeList []*Element 45 | 46 | type NodeBase struct { 47 | v js.Value 48 | funcs []js.Func 49 | } 50 | 51 | // JSValue implements js.Wrapper. 52 | func (e NodeBase) JSValue() js.Ref { 53 | return e.v.JSValue() 54 | } 55 | 56 | func (e *NodeBase) Remove() { 57 | e.ParentNode().RemoveChild(e) 58 | for _, c := range e.funcs { 59 | c.Release() 60 | } 61 | e.funcs = nil 62 | } 63 | 64 | func (e *NodeBase) AddEventListener(typ string, h EventHandler) { 65 | cb := js.NewEventCallback(func(v js.Value) { 66 | h(convertEvent(v)) 67 | }) 68 | e.funcs = append(e.funcs, cb) 69 | e.v.Call("addEventListener", typ, cb) 70 | } 71 | 72 | func (e *NodeBase) AddErrorListener(h func(err error)) { 73 | e.AddEventListener("error", func(e Event) { 74 | ConsoleLog(e.JSValue()) 75 | h(js.Error{Value: e.JSValue()}) 76 | }) 77 | } 78 | 79 | func (e *NodeBase) BaseURI() string { 80 | return e.v.Get("baseURI").String() 81 | } 82 | 83 | func (e *NodeBase) NodeName() string { 84 | return e.v.Get("nodeName").String() 85 | } 86 | 87 | func (e *NodeBase) NodeType() NodeType { 88 | return NodeType(e.v.Get("nodeType").Int()) 89 | } 90 | 91 | func (e *NodeBase) ChildNodes() NodeList { 92 | return AsNodeList(e.v.Get("childNodes")) 93 | } 94 | 95 | func (e *NodeBase) ParentNode() Node { 96 | return AsElement(e.v.Get("parentNode")) 97 | } 98 | 99 | func (e *NodeBase) ParentElement() *Element { 100 | return AsElement(e.v.Get("parentElement")) 101 | } 102 | 103 | func (e *NodeBase) TextContent() string { 104 | return e.v.Get("textContent").String() 105 | } 106 | 107 | func (e *NodeBase) SetTextContent(s string) { 108 | e.v.Set("textContent", s) 109 | } 110 | 111 | func (e *NodeBase) AppendChild(n Node) { 112 | e.v.Call("appendChild", n) 113 | } 114 | 115 | func (e *NodeBase) Contains(n Node) bool { 116 | return e.v.Call("contains", n).Bool() 117 | } 118 | 119 | func (e *NodeBase) IsEqualNode(n Node) bool { 120 | return e.v.Call("isEqualNode", n).Bool() 121 | } 122 | 123 | func (e *NodeBase) IsSameNode(n Node) bool { 124 | return e.v.Call("isSameNode", n).Bool() 125 | } 126 | 127 | func (e *NodeBase) RemoveChild(n Node) Node { 128 | return AsElement(e.v.Call("removeChild", n)) 129 | } 130 | 131 | func (e *NodeBase) ReplaceChild(n, old Node) Node { 132 | return AsElement(e.v.Call("replaceChild", n, old)) 133 | } 134 | -------------------------------------------------------------------------------- /extension/chrome/native/native_js.go: -------------------------------------------------------------------------------- 1 | //+build wasm 2 | 3 | package native 4 | 5 | import ( 6 | "encoding/base64" 7 | "fmt" 8 | "io" 9 | 10 | "github.com/dennwc/dom/js" 11 | ) 12 | 13 | // NewApp returns a application instance with a name, as specified in the extension manifest. 14 | func NewApp(name string) *App { 15 | return &App{name: name} 16 | } 17 | 18 | // Msg is a type of the message. 19 | type Msg = map[string]interface{} 20 | 21 | // App represents a native messaging connector running on the host. 22 | type App struct { 23 | name string 24 | } 25 | 26 | // Send runs a native extension, passes a single message to it and waits for the response. 27 | // Native connector will likely be killed after the response is received. 28 | func (app *App) Send(o Msg) js.Value { 29 | resp := make(chan js.Value, 1) 30 | var cb js.Func 31 | cb = js.FuncOf(func(this js.Value, args []js.Value) interface{} { 32 | cb.Release() 33 | if len(args) != 0 { 34 | resp <- args[0] 35 | } else { 36 | resp <- js.Value{} 37 | } 38 | return nil 39 | }) 40 | js.Get("chrome").Get("runtime").Call("sendNativeMessage", app.name, o, cb) 41 | return <-resp 42 | } 43 | 44 | // SendBinary sends a single binary message and receives a response. 45 | // 46 | // The format of the binary messages is not defined in any spec, 47 | // thus a custom message format is used to transfer binary data. 48 | func (app *App) SendBinary(p []byte) ([]byte, error) { 49 | resp := app.Send(Msg{ 50 | "d": base64.StdEncoding.EncodeToString(p), 51 | }) 52 | if resp.IsUndefined() { 53 | return nil, fmt.Errorf("application failed") 54 | } 55 | return base64.StdEncoding.DecodeString(resp.Get("d").String()) 56 | } 57 | 58 | // Dial runs a native extension connector to send multiple messages. 59 | // This is more efficient than App.Send because an extension won't be killed after each received message. 60 | func (app *App) Dial() (*Conn, error) { 61 | port := js.Get("chrome").Get("runtime").Call("connectNative", app.name) 62 | c := &Conn{ 63 | app: app, port: port, 64 | r: make(chan js.Value, 1), 65 | } 66 | c.cRecv = js.FuncOf(c.recv) 67 | c.cDisc = js.FuncOf(c.disconnect) 68 | port.Get("onMessage").Call("addListener", c.cRecv) 69 | port.Get("onDisconnect").Call("addListener", c.cDisc) 70 | return c, nil 71 | } 72 | 73 | // Conn represents a connection to a native extension. 74 | type Conn struct { 75 | app *App 76 | port js.Value 77 | 78 | err error 79 | r chan js.Value 80 | 81 | cRecv, cDisc js.Func 82 | } 83 | 84 | func (c *Conn) recv(_ js.Value, args []js.Value) interface{} { 85 | c.r <- args[0] 86 | return nil 87 | } 88 | func (c *Conn) disconnect(_ js.Value, _ []js.Value) interface{} { 89 | c.cDisc.Release() 90 | c.cRecv.Release() 91 | c.err = io.EOF 92 | close(c.r) 93 | return nil 94 | } 95 | 96 | // Recv receives a single message. 97 | func (c *Conn) Recv() (js.Value, error) { 98 | if c.err != nil { 99 | return js.Value{}, c.err 100 | } 101 | v, ok := <-c.r 102 | if !ok { 103 | return js.Value{}, c.err 104 | } 105 | return v, nil 106 | } 107 | 108 | // Send sends a single message. 109 | func (c *Conn) Send(m Msg) error { 110 | c.port.Call("postMessage", m) 111 | return nil 112 | } 113 | 114 | // RecvBinary receives a single binary message. 115 | // 116 | // The format of the binary messages is not defined in any spec, 117 | // thus a custom message format is used to transfer binary data. 118 | func (c *Conn) RecvBinary() ([]byte, error) { 119 | v, err := c.Recv() 120 | if err != nil { 121 | return nil, err 122 | } 123 | return base64.StdEncoding.DecodeString(v.Get("d").String()) 124 | } 125 | 126 | // SendBinary sends a single binary message. 127 | // 128 | // The format of the binary messages is not defined in any spec, 129 | // thus a custom message format is used to transfer binary data. 130 | func (c *Conn) SendBinary(p []byte) error { 131 | return c.Send(Msg{ 132 | "d": base64.StdEncoding.EncodeToString(p), 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "github.com/dennwc/dom/js" 5 | ) 6 | 7 | type EventTarget interface { 8 | js.Wrapper 9 | AddEventListener(typ string, h EventHandler) 10 | // TODO: removeEventListener 11 | // TODO: dispatchEvent 12 | } 13 | 14 | type Event interface { 15 | js.Wrapper 16 | Bubbles() bool 17 | Cancelable() bool 18 | Composed() bool 19 | CurrentTarget() *Element 20 | DefaultPrevented() bool 21 | Target() *Element 22 | Type() string 23 | IsTrusted() bool 24 | Path() NodeList 25 | 26 | PreventDefault() 27 | StopPropagation() 28 | StopImmediatePropagation() 29 | } 30 | 31 | type EventHandler func(Event) 32 | 33 | type EventConstructor func(e BaseEvent) Event 34 | 35 | func RegisterEventType(typ string, fnc EventConstructor) { 36 | cl := js.Get(typ) 37 | if !cl.Valid() { 38 | return 39 | } 40 | eventClasses = append(eventClasses, eventClass{ 41 | Class: cl, New: fnc, 42 | }) 43 | } 44 | 45 | func init() { 46 | RegisterEventType("MouseEvent", func(e BaseEvent) Event { 47 | return &MouseEvent{e} 48 | }) 49 | } 50 | 51 | type eventClass struct { 52 | Class js.Value 53 | New EventConstructor 54 | } 55 | 56 | var ( 57 | eventClasses []eventClass 58 | ) 59 | 60 | func convertEvent(v js.Value) Event { 61 | e := BaseEvent{v: v} 62 | // TODO: get class name directly 63 | for _, cl := range eventClasses { 64 | if v.InstanceOf(cl.Class) { 65 | return cl.New(e) 66 | } 67 | } 68 | return &e 69 | } 70 | 71 | type BaseEvent struct { 72 | v js.Value 73 | } 74 | 75 | func (e *BaseEvent) getBool(name string) bool { 76 | return e.v.Get(name).Bool() 77 | } 78 | func (e *BaseEvent) Bubbles() bool { 79 | return e.getBool("bubbles") 80 | } 81 | 82 | func (e *BaseEvent) Cancelable() bool { 83 | return e.getBool("cancelable") 84 | } 85 | 86 | func (e *BaseEvent) Composed() bool { 87 | return e.getBool("composed") 88 | } 89 | 90 | func (e *BaseEvent) CurrentTarget() *Element { 91 | return AsElement(e.v.Get("currentTarget")) 92 | } 93 | 94 | func (e *BaseEvent) DefaultPrevented() bool { 95 | return e.getBool("defaultPrevented") 96 | } 97 | 98 | func (e *BaseEvent) IsTrusted() bool { 99 | return e.getBool("isTrusted") 100 | } 101 | 102 | func (e *BaseEvent) JSValue() js.Ref { 103 | return e.v.JSValue() 104 | } 105 | 106 | func (e *BaseEvent) Type() string { 107 | return e.v.Get("type").String() 108 | } 109 | 110 | func (e *BaseEvent) Target() *Element { 111 | return AsElement(e.v.Get("target")) 112 | } 113 | 114 | func (e *BaseEvent) Path() NodeList { 115 | return AsNodeList(e.v.Get("path")) 116 | } 117 | 118 | func (e *BaseEvent) PreventDefault() { 119 | e.v.Call("preventDefault") 120 | } 121 | func (e *BaseEvent) StopPropagation() { 122 | e.v.Call("stopPropagation") 123 | } 124 | func (e *BaseEvent) StopImmediatePropagation() { 125 | e.v.Call("stopImmediatePropagation") 126 | } 127 | 128 | type MouseEventHandler func(*MouseEvent) 129 | 130 | type MouseEvent struct { 131 | BaseEvent 132 | } 133 | 134 | func (e *MouseEvent) getPos(nameX, nameY string) Point { 135 | x := e.v.Get(nameX).Int() 136 | y := e.v.Get(nameY).Int() 137 | return Point{X: x, Y: y} 138 | } 139 | 140 | func (e *MouseEvent) getPosPref(pref string) Point { 141 | return e.getPos(pref+"X", pref+"Y") 142 | } 143 | 144 | const ( 145 | MouseLeft = MouseButton(0) 146 | ) 147 | 148 | type MouseButton int 149 | 150 | func (e *MouseEvent) Button() MouseButton { 151 | return MouseButton(e.v.Get("button").Int()) 152 | } 153 | 154 | func (e *MouseEvent) ClientPos() Point { 155 | return e.getPosPref("client") 156 | } 157 | 158 | func (e *MouseEvent) OffsetPos() Point { 159 | return e.getPosPref("offset") 160 | } 161 | 162 | func (e *MouseEvent) PagePos() Point { 163 | return e.getPosPref("page") 164 | } 165 | 166 | func (e *MouseEvent) ScreenPos() Point { 167 | return e.getPosPref("screen") 168 | } 169 | 170 | func (e *MouseEvent) AltKey() bool { 171 | return e.v.Get("altKey").Bool() 172 | } 173 | 174 | func (e *MouseEvent) CtrlKey() bool { 175 | return e.v.Get("ctrlKey").Bool() 176 | } 177 | 178 | func (e *MouseEvent) ShiftKey() bool { 179 | return e.v.Get("shiftKey").Bool() 180 | } 181 | 182 | func (e *MouseEvent) MetaKey() bool { 183 | return e.v.Get("metaKey").Bool() 184 | } 185 | -------------------------------------------------------------------------------- /examples/webrtc/main.go: -------------------------------------------------------------------------------- 1 | //+build wasm,js 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "time" 9 | 10 | "github.com/dennwc/dom/net/webrtc" 11 | ) 12 | 13 | func main() { 14 | sig := NewChannel() 15 | 16 | go Alice(sig) 17 | Bob(sig) 18 | } 19 | 20 | func Alice(sig webrtc.Signalling) { 21 | const name = "alice" 22 | p1 := webrtc.New(name, sig) 23 | 24 | fmt.Println(name + ": peer discovery started") 25 | peers, err := p1.Discover() 26 | if err != nil { 27 | panic(err) 28 | } 29 | defer peers.Close() 30 | 31 | fmt.Println(name + ": waiting for peers") 32 | info, err := peers.Accept() 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | pname := info.UID() 38 | fmt.Printf(name+": dialing peer %q\n", pname) 39 | conn, err := info.Dial() 40 | if err != nil { 41 | panic(err) 42 | } 43 | defer conn.Close() 44 | 45 | fmt.Println(name + ": connected!") 46 | _, err = fmt.Fprintf(conn, "hello from %q\n", name) 47 | if err != nil { 48 | panic(err) 49 | } 50 | fmt.Println(name + ": sent data") 51 | 52 | buf := make([]byte, 128) 53 | for { 54 | n, err := conn.Read(buf) 55 | if err == io.EOF { 56 | break 57 | } else if err != nil { 58 | panic(err) 59 | } 60 | fmt.Printf(name+": msg from %q: %q\n", pname, string(buf[:n])) 61 | } 62 | } 63 | 64 | func Bob(sig webrtc.Signalling) { 65 | const name = "bob" 66 | 67 | p2 := webrtc.New(name, sig) 68 | fmt.Println(name + ": listening for offers") 69 | peers, err := p2.Listen() 70 | if err != nil { 71 | panic(err) 72 | } 73 | defer peers.Close() 74 | 75 | info, err := peers.Accept() 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | pname := info.UID() 81 | fmt.Printf(name+": dialing peer %q\n", pname) 82 | conn, err := info.Dial() 83 | if err != nil { 84 | panic(err) 85 | } 86 | defer conn.Close() 87 | 88 | fmt.Println(name + ": connected!") 89 | _, err = fmt.Fprintf(conn, "hello from %q\n", name) 90 | if err != nil { 91 | panic(err) 92 | } 93 | fmt.Println(name + ": sent data") 94 | 95 | buf := make([]byte, 128) 96 | n, err := conn.Read(buf) 97 | if err != nil { 98 | panic(err) 99 | } 100 | fmt.Printf(name+": msg from %q: %q\n", pname, string(buf[:n])) 101 | 102 | for { 103 | _, err = conn.Write([]byte(time.Now().String() + "\n")) 104 | if err != nil { 105 | panic(err) 106 | } 107 | time.Sleep(time.Second * 5) 108 | } 109 | } 110 | 111 | // NewChannel creates a new signalling channel. It expects exactly one call to Broadcast and exactly one call to Listen. 112 | func NewChannel() webrtc.Signalling { 113 | return &signalChannel{ 114 | broadcast: make(chan webrtc.Signal, 1), 115 | accept: make(chan webrtc.Signal, 1), 116 | } 117 | } 118 | 119 | type signalChannel struct { 120 | broadcast chan webrtc.Signal 121 | accept chan webrtc.Signal 122 | } 123 | 124 | func (b *signalChannel) Broadcast(s webrtc.Signal) (webrtc.AnswerStream, error) { 125 | b.broadcast <- s 126 | close(b.broadcast) 127 | return &answers{accept: b.accept}, nil 128 | } 129 | 130 | type answers struct { 131 | accept <-chan webrtc.Signal 132 | } 133 | 134 | func (a *answers) Next() (webrtc.Signal, error) { 135 | s, ok := <-a.accept 136 | if !ok { 137 | return webrtc.Signal{}, io.EOF 138 | } 139 | return s, nil 140 | } 141 | 142 | func (a *answers) Close() error { 143 | ch := make(chan webrtc.Signal) 144 | close(ch) 145 | a.accept = ch 146 | return nil 147 | } 148 | 149 | func (b *signalChannel) Listen(uid string) (webrtc.OfferStream, error) { 150 | return &offers{broadcast: b.broadcast, accept: b.accept}, nil 151 | } 152 | 153 | type offers struct { 154 | broadcast <-chan webrtc.Signal 155 | accept chan<- webrtc.Signal 156 | } 157 | 158 | func (o *offers) Next() (webrtc.Offer, error) { 159 | s, ok := <-o.broadcast 160 | if !ok { 161 | return nil, io.EOF 162 | } 163 | return &offer{accept: o.accept, s: s}, nil 164 | } 165 | 166 | func (o *offers) Close() error { 167 | ch := make(chan webrtc.Signal) 168 | close(ch) 169 | o.broadcast = ch 170 | return nil 171 | } 172 | 173 | type offer struct { 174 | accept chan<- webrtc.Signal 175 | s webrtc.Signal 176 | } 177 | 178 | func (o *offer) Answer(s webrtc.Signal) error { 179 | o.accept <- s 180 | close(o.accept) 181 | o.accept = nil 182 | return nil 183 | } 184 | 185 | func (o *offer) Info() webrtc.Signal { 186 | return o.s 187 | } 188 | -------------------------------------------------------------------------------- /js/js_host.go: -------------------------------------------------------------------------------- 1 | //+build !wasm 2 | 3 | package js 4 | 5 | import "errors" 6 | 7 | var ( 8 | global = Ref{ref: 2} 9 | null = Ref{ref: 1} 10 | undefined = Ref{ref: 0} 11 | ) 12 | 13 | var errNotImplemented = errors.New("not implemented; build with GOARCH=wasm") 14 | 15 | func valueOf(o interface{}) Ref { 16 | return undefined 17 | } 18 | 19 | type Wrapper = interface { 20 | // JSValue returns a JavaScript value associated with an object. 21 | JSValue() Ref 22 | } 23 | 24 | // Func is a wrapped Go function to be called by JavaScript. 25 | type Func struct { 26 | Value Ref 27 | } 28 | 29 | // Release frees up resources allocated for the function. 30 | // The function must not be invoked after calling Release. 31 | func (f Func) Release() { 32 | panic(errNotImplemented) 33 | } 34 | 35 | func funcOf(fnc func(this Ref, refs []Ref) interface{}) Func { 36 | panic(errNotImplemented) 37 | } 38 | 39 | // Ref is an alias for syscall/js.Value. 40 | type Ref struct { 41 | ref uint64 42 | } 43 | 44 | // Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator, 45 | // except that it returns TypeNull instead of TypeObject for null. 46 | func (v Ref) Type() Type { 47 | panic(errNotImplemented) 48 | } 49 | 50 | // Get returns the JavaScript property p of value v. 51 | func (v Ref) Get(k string) Ref { 52 | return undefined 53 | } 54 | 55 | // Set sets the JavaScript property p of value v to ValueOf(x). 56 | func (v Ref) Set(p string, x interface{}) { 57 | panic(errNotImplemented) 58 | } 59 | 60 | // Index returns JavaScript index i of value v. 61 | func (v Ref) Index(i int) Ref { 62 | panic(errNotImplemented) 63 | } 64 | 65 | // SetIndex sets the JavaScript index i of value v to ValueOf(x). 66 | func (v Ref) SetIndex(i int, x interface{}) { 67 | panic(errNotImplemented) 68 | } 69 | 70 | // Length returns the JavaScript property "length" of v. 71 | func (v Ref) Length() int { 72 | return 0 73 | } 74 | 75 | // Call does a JavaScript call to the method m of value v with the given arguments. 76 | // It panics if v has no method m. 77 | // The arguments get mapped to JavaScript values according to the ValueOf function. 78 | func (v Ref) Call(m string, args ...interface{}) Ref { 79 | panic(errNotImplemented) 80 | } 81 | 82 | // Invoke does a JavaScript call of the value v with the given arguments. 83 | // It panics if v is not a function. 84 | // The arguments get mapped to JavaScript values according to the ValueOf function. 85 | func (v Ref) Invoke(args ...interface{}) Ref { 86 | panic(errNotImplemented) 87 | } 88 | 89 | // New uses JavaScript's "new" operator with value v as constructor and the given arguments. 90 | // It panics if v is not a function. 91 | // The arguments get mapped to JavaScript values according to the ValueOf function. 92 | func (v Ref) New(args ...interface{}) Ref { 93 | panic(errNotImplemented) 94 | } 95 | 96 | // Float returns the value v as a float64. It panics if v is not a JavaScript number. 97 | func (v Ref) Float() float64 { 98 | panic(errNotImplemented) 99 | } 100 | 101 | // Int returns the value v truncated to an int. It panics if v is not a JavaScript number. 102 | func (v Ref) Int() int { 103 | panic(errNotImplemented) 104 | } 105 | 106 | // Bool returns the value v as a bool. It panics if v is not a JavaScript boolean. 107 | func (v Ref) Bool() bool { 108 | panic(errNotImplemented) 109 | } 110 | 111 | // Truthy returns the JavaScript "truthiness" of the value v. In JavaScript, 112 | // false, 0, "", null, undefined, and NaN are "falsy", and everything else is 113 | // "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy. 114 | func (v Ref) Truthy() bool { 115 | panic(errNotImplemented) 116 | } 117 | 118 | // String returns the value v converted to string according to JavaScript type conversions. 119 | func (v Ref) String() string { 120 | panic(errNotImplemented) 121 | } 122 | 123 | // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator. 124 | func (v Ref) InstanceOf(t Ref) bool { 125 | panic(errNotImplemented) 126 | } 127 | 128 | // Error is an alias for syscall/js.Error. 129 | type Error struct { 130 | Value Ref 131 | } 132 | 133 | // Error implements the error interface. 134 | func (e Error) Error() string { 135 | return "JavaScript error: undefined" 136 | } 137 | 138 | // Type is a type name of a JS value, as returned by "typeof". 139 | type Type int 140 | 141 | const ( 142 | TypeObject = Type(iota + 1) 143 | TypeFunction 144 | ) 145 | 146 | func typedArrayOf(slice interface{}) Ref { 147 | panic(errNotImplemented) 148 | } 149 | 150 | func releaseTypedArray(v Ref) { 151 | panic(errNotImplemented) 152 | } 153 | -------------------------------------------------------------------------------- /extension/chrome/tabs.go: -------------------------------------------------------------------------------- 1 | package chrome 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | type TabOptions struct { 6 | Active bool 7 | } 8 | 9 | type AllTabs interface { 10 | js.Wrapper 11 | GetCurrent() Tab 12 | GetSelected(window WindowID) Tab 13 | GetAllInWindow(window WindowID) []Tab 14 | Create(url string, opt *TabOptions) Tab 15 | } 16 | 17 | func Tabs() AllTabs { 18 | return tabs() 19 | } 20 | 21 | func tabs() jsTabs { 22 | return jsTabs{v: chrome.Get("tabs")} 23 | } 24 | 25 | type jsTabs struct { 26 | v js.Value 27 | } 28 | 29 | func (t jsTabs) JSValue() js.Ref { 30 | return t.v.JSValue() 31 | } 32 | 33 | func (t jsTabs) callAsync(name string, args ...interface{}) js.Value { 34 | ch := make(chan js.Value, 1) 35 | cb := js.NewEventCallback(func(v js.Value) { 36 | ch <- v 37 | }) 38 | defer cb.Release() 39 | args = append(args, cb) 40 | t.v.Call(name, args...) 41 | return <-ch 42 | } 43 | 44 | func (t jsTabs) GetCurrent() Tab { 45 | v := t.callAsync("getCurrent") 46 | if !v.Valid() { 47 | return nil 48 | } 49 | return jsTab{v} 50 | } 51 | 52 | func (t jsTabs) GetSelected(window WindowID) Tab { 53 | var win interface{} 54 | if window != 0 { 55 | win = int(window) 56 | } 57 | v := t.callAsync("getSelected", win) 58 | if !v.Valid() { 59 | return nil 60 | } 61 | return jsTab{v} 62 | } 63 | 64 | func (t jsTabs) GetAllInWindow(window WindowID) []Tab { 65 | var win interface{} 66 | if window != 0 { 67 | win = int(window) 68 | } 69 | v := t.callAsync("getAllInWindow", win) 70 | vals := v.Slice() 71 | tabs := make([]Tab, 0, len(vals)) 72 | for _, v := range vals { 73 | tabs = append(tabs, jsTab{v}) 74 | } 75 | return tabs 76 | } 77 | 78 | func (t jsTabs) Create(url string, opt *TabOptions) Tab { 79 | obj := js.Obj{ 80 | "url": url, 81 | } 82 | if opt != nil { 83 | obj["active"] = opt.Active 84 | } 85 | v := t.callAsync("create", obj) 86 | return jsTab{v} 87 | } 88 | 89 | type Tab interface { 90 | js.Wrapper 91 | ID() int 92 | Active() bool 93 | Incognito() bool 94 | Highlighted() bool 95 | Pinned() bool 96 | Selected() bool 97 | Index() int 98 | WindowID() int 99 | URL() string 100 | Title() string 101 | Size() (w, h int) 102 | 103 | ExecuteFile(path string) (js.Value, error) 104 | ExecuteCode(code string) (js.Value, error) 105 | } 106 | 107 | func AsTab(v js.Value) Tab { 108 | return jsTab{v: v} 109 | } 110 | 111 | type jsTab struct { 112 | v js.Value 113 | } 114 | 115 | func (t jsTab) JSValue() js.Ref { 116 | return t.v.JSValue() 117 | } 118 | 119 | func (t jsTab) ID() int { 120 | return t.v.Get("id").Int() 121 | } 122 | 123 | func (t jsTab) Active() bool { 124 | return t.v.Get("active").Bool() 125 | } 126 | 127 | func (t jsTab) Incognito() bool { 128 | return t.v.Get("incognito").Bool() 129 | } 130 | 131 | func (t jsTab) Highlighted() bool { 132 | return t.v.Get("highlighted").Bool() 133 | } 134 | 135 | func (t jsTab) Pinned() bool { 136 | return t.v.Get("pinned").Bool() 137 | } 138 | 139 | func (t jsTab) Selected() bool { 140 | return t.v.Get("selected").Bool() 141 | } 142 | 143 | func (t jsTab) WindowID() int { 144 | return t.v.Get("windowId").Int() 145 | } 146 | 147 | func (t jsTab) Index() int { 148 | return t.v.Get("index").Int() 149 | } 150 | 151 | func (t jsTab) Title() string { 152 | return t.v.Get("title").String() 153 | } 154 | 155 | func (t jsTab) URL() string { 156 | return t.v.Get("url").String() 157 | } 158 | 159 | func (t jsTab) Size() (w, h int) { 160 | w = t.v.Get("width").Int() 161 | h = t.v.Get("height").Int() 162 | return 163 | } 164 | 165 | func (t jsTab) executeScript(obj interface{}) ([]js.Value, error) { 166 | id := t.ID() 167 | ch := make(chan []js.Value, 1) 168 | errc := make(chan error, 1) 169 | cb := js.CallbackOf(func(args []js.Value) { 170 | err := lastError() 171 | if err != nil { 172 | errc <- err 173 | return 174 | } 175 | v := args[0] 176 | if !v.Valid() { 177 | ch <- nil 178 | } else { 179 | ch <- v.Slice() 180 | } 181 | }) 182 | defer cb.Release() 183 | tabs().v.Call("executeScript", id, obj, cb) 184 | select { 185 | case err := <-errc: 186 | return nil, err 187 | case res := <-ch: 188 | return res, nil 189 | } 190 | } 191 | 192 | func (t jsTab) executeScriptOne(obj interface{}) (js.Value, error) { 193 | res, err := t.executeScript(obj) 194 | if err != nil || len(res) == 0 { 195 | return js.Value{}, err 196 | } 197 | return res[0], nil 198 | } 199 | 200 | func (t jsTab) ExecuteFile(path string) (js.Value, error) { 201 | return t.executeScriptOne(js.Obj{"file": path}) 202 | } 203 | 204 | func (t jsTab) ExecuteCode(code string) (js.Value, error) { 205 | return t.executeScriptOne(js.Obj{"code": code}) 206 | } 207 | -------------------------------------------------------------------------------- /net/ws/wsconn.go: -------------------------------------------------------------------------------- 1 | //+build !js 2 | 3 | // Package ws provides a functionality similar to Go net package on top of WebSockets. 4 | package ws 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "strings" 14 | "time" 15 | 16 | "github.com/gorilla/websocket" 17 | ) 18 | 19 | func newServer(def http.Handler) *wsServer { 20 | return &wsServer{ 21 | def: def, 22 | stop: make(chan struct{}), 23 | errc: make(chan error, 1), 24 | conn: make(chan net.Conn), 25 | } 26 | } 27 | 28 | // Listen listens for incoming connections on a given URL and server other requests with def handler. 29 | func Listen(addr string, def http.Handler) (net.Listener, error) { 30 | u, err := url.Parse(addr) 31 | if err != nil { 32 | return nil, err 33 | } 34 | srv := newServer(def) 35 | mux := http.NewServeMux() 36 | // TODO: support HTTPS 37 | srv.h = &http.Server{ 38 | Handler: mux, 39 | } 40 | if u.Path == "" { 41 | u.Path = "/" 42 | } 43 | mux.HandleFunc(u.Path, srv.handleWS) 44 | if def != nil && strings.Trim(u.Path, "/") != "" { 45 | mux.Handle("/", def) 46 | } 47 | lis, err := net.Listen("tcp", u.Host) 48 | if err != nil { 49 | return nil, err 50 | } 51 | go func() { 52 | defer close(srv.errc) 53 | srv.errc <- srv.h.Serve(lis) 54 | }() 55 | return srv, nil 56 | } 57 | 58 | type wsServer struct { 59 | def http.Handler 60 | h *http.Server 61 | stop chan struct{} 62 | errc chan error 63 | conn chan net.Conn 64 | } 65 | 66 | func (s *wsServer) Accept() (net.Conn, error) { 67 | select { 68 | case <-s.stop: 69 | return nil, fmt.Errorf("server stopped") 70 | case err := <-s.errc: 71 | _ = s.Close() 72 | return nil, err 73 | case c := <-s.conn: 74 | return c, nil 75 | } 76 | } 77 | 78 | func (s *wsServer) Close() error { 79 | select { 80 | case <-s.stop: 81 | default: 82 | close(s.stop) 83 | } 84 | if s.h != nil { 85 | return s.h.Close() 86 | } 87 | return nil 88 | } 89 | 90 | func (s *wsServer) Addr() net.Addr { 91 | return wsAddr{} 92 | } 93 | 94 | var upgrader = websocket.Upgrader{ 95 | CheckOrigin: func(r *http.Request) bool { 96 | return true // TODO: make it configurable 97 | }, 98 | } 99 | 100 | func (s *wsServer) handleWS(w http.ResponseWriter, r *http.Request) { 101 | if s.def != nil && !websocket.IsWebSocketUpgrade(r) { 102 | s.def.ServeHTTP(w, r) 103 | return 104 | } 105 | conn, err := upgrader.Upgrade(w, r, nil) 106 | if err != nil { 107 | log.Println(err) 108 | return 109 | } 110 | done := make(chan struct{}) 111 | c := &wsConn{c: conn, done: done} 112 | defer conn.Close() 113 | select { 114 | case <-s.stop: 115 | return 116 | case s.conn <- c: 117 | select { 118 | case <-s.stop: 119 | case <-done: 120 | } 121 | } 122 | } 123 | 124 | var dialer = &websocket.Dialer{} 125 | 126 | // Dial connects to a WebSocket on a specified URL. 127 | func Dial(addr string) (net.Conn, error) { 128 | conn, _, err := dialer.Dial(addr, nil) 129 | if err != nil { 130 | return nil, err 131 | } 132 | return &wsConn{c: conn}, nil 133 | } 134 | 135 | type wsConn struct { 136 | c *websocket.Conn 137 | cur io.Reader 138 | done chan struct{} 139 | } 140 | 141 | func (c *wsConn) Read(b []byte) (int, error) { 142 | for { 143 | if c.cur != nil { 144 | n, err := c.cur.Read(b) 145 | if err == nil || err != io.EOF { 146 | return n, err 147 | } else if err == io.EOF && n != 0 { 148 | return n, nil 149 | } 150 | // EOF, n == 0 151 | c.cur = nil 152 | } 153 | _, r, err := c.c.NextReader() 154 | if err != nil { 155 | return 0, err 156 | } 157 | c.cur = r 158 | } 159 | } 160 | 161 | func (c *wsConn) Write(b []byte) (int, error) { 162 | // TODO: buffer writes 163 | err := c.c.WriteMessage(websocket.BinaryMessage, b) 164 | if err != nil { 165 | return 0, err 166 | } 167 | return len(b), nil 168 | } 169 | 170 | func (c *wsConn) Close() error { 171 | if c.done == nil { 172 | return c.c.Close() 173 | } 174 | select { 175 | case <-c.done: 176 | default: 177 | close(c.done) 178 | } 179 | return nil 180 | } 181 | 182 | func (c *wsConn) LocalAddr() net.Addr { 183 | return wsAddr{} 184 | } 185 | 186 | func (c *wsConn) RemoteAddr() net.Addr { 187 | return wsAddr{} 188 | } 189 | 190 | func (c *wsConn) SetDeadline(t time.Time) error { 191 | if err := c.SetReadDeadline(t); err != nil { 192 | return err 193 | } 194 | if err := c.SetWriteDeadline(t); err != nil { 195 | return err 196 | } 197 | return nil 198 | } 199 | 200 | func (c *wsConn) SetReadDeadline(t time.Time) error { 201 | return c.c.SetReadDeadline(t) 202 | } 203 | 204 | func (c *wsConn) SetWriteDeadline(t time.Time) error { 205 | return c.c.SetWriteDeadline(t) 206 | } 207 | -------------------------------------------------------------------------------- /js/funcs.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | // CallbackOf returns a wrapped callback function. 4 | // 5 | // Invoking the callback in JavaScript will queue the Go function fn for execution. 6 | // This execution happens asynchronously on a special goroutine that handles all callbacks and preserves 7 | // the order in which the callbacks got called. 8 | // As a consequence, if one callback blocks this goroutine, other callbacks will not be processed. 9 | // A blocking callback should therefore explicitly start a new goroutine. 10 | // 11 | // Callback.Release must be called to free up resources when the callback will not be used any more. 12 | func CallbackOf(fnc func(v []Value)) Func { 13 | return funcOf(func(this Ref, refs []Ref) interface{} { 14 | vals := make([]Value, 0, len(refs)) 15 | for _, ref := range refs { 16 | vals = append(vals, Value{ref}) 17 | } 18 | fnc(vals) 19 | return nil 20 | }) 21 | } 22 | 23 | // AsyncCallbackOf returns a wrapped callback function. 24 | // 25 | // Invoking the callback in JavaScript will queue the Go function fn for execution. 26 | // This execution happens asynchronously. 27 | // 28 | // Callback.Release must be called to free up resources when the callback will not be used any more. 29 | func AsyncCallbackOf(fnc func(v []Value)) Func { 30 | return funcOf(func(this Ref, refs []Ref) interface{} { 31 | vals := make([]Value, 0, len(refs)) 32 | for _, ref := range refs { 33 | vals = append(vals, Value{ref}) 34 | } 35 | go fnc(vals) 36 | return nil 37 | }) 38 | } 39 | 40 | // FuncOf returns a wrapped function. 41 | // 42 | // Invoking the JavaScript function will synchronously call the Go function fn with the value of JavaScript's 43 | // "this" keyword and the arguments of the invocation. 44 | // The return value of the invocation is the result of the Go function mapped back to JavaScript according to ValueOf. 45 | // 46 | // A wrapped function triggered during a call from Go to JavaScript gets executed on the same goroutine. 47 | // A wrapped function triggered by JavaScript's event loop gets executed on an extra goroutine. 48 | // Blocking operations in the wrapped function will block the event loop. 49 | // As a consequence, if one wrapped function blocks, other wrapped funcs will not be processed. 50 | // A blocking function should therefore explicitly start a new goroutine. 51 | // 52 | // Func.Release must be called to free up resources when the function will not be used any more. 53 | func FuncOf(fnc func(this Value, args []Value) interface{}) Func { 54 | return funcOf(func(this Ref, refs []Ref) interface{} { 55 | vals := make([]Value, 0, len(refs)) 56 | for _, ref := range refs { 57 | vals = append(vals, Value{ref}) 58 | } 59 | v := fnc(Value{this}, vals) 60 | return ValueOf(v).Ref 61 | }) 62 | } 63 | 64 | // NativeFuncOf creates a function from a JS code string. 65 | // 66 | // Example: 67 | // NativeFuncOf("a", "b", "return a+b").Call(a, b) 68 | func NativeFuncOf(argsAndCode ...string) Value { 69 | args := make([]interface{}, len(argsAndCode)) 70 | for i, v := range argsAndCode { 71 | args[i] = v 72 | } 73 | return New("Function", args...) 74 | } 75 | 76 | // NewEventCallback is a shorthand for NewEventCallbackFlags with default flags. 77 | func NewEventCallback(fnc func(v Value)) Func { 78 | return CallbackOf(func(v []Value) { 79 | fnc(v[0]) 80 | }) 81 | } 82 | 83 | // NewFuncGroup creates a new function group on this object. 84 | func (v Value) NewFuncGroup() *FuncGroup { 85 | return &FuncGroup{ 86 | v: v, 87 | } 88 | } 89 | 90 | // FuncGroup is a list of Go functions attached to an object. 91 | type FuncGroup struct { 92 | v Value 93 | funcs []Func 94 | } 95 | 96 | func (g *FuncGroup) Add(cb Func) { 97 | g.funcs = append(g.funcs, cb) 98 | } 99 | func (g *FuncGroup) Set(name string, fnc func([]Value)) { 100 | cb := CallbackOf(fnc) 101 | g.v.Set(name, cb) 102 | g.Add(cb) 103 | } 104 | func (g *FuncGroup) addEventListener(event string, cb Func) { 105 | g.v.Call("addEventListener", event, cb) 106 | } 107 | func (g *FuncGroup) removeEventListener(event string, cb Func) { 108 | g.v.Call("removeEventListener", event, cb) 109 | } 110 | func (g *FuncGroup) AddEventListener(event string, fnc func(Value)) { 111 | cb := NewEventCallback(fnc) 112 | g.addEventListener(event, cb) 113 | g.Add(cb) 114 | } 115 | func (g *FuncGroup) ErrorEvent(fnc func(error)) { 116 | g.AddEventListener("onerror", func(v Value) { 117 | fnc(Error{v.Ref}) 118 | }) 119 | } 120 | func (g *FuncGroup) ErrorEventChan() <-chan error { 121 | ch := make(chan error, 1) 122 | g.ErrorEvent(func(err error) { 123 | select { 124 | case ch <- err: 125 | default: 126 | panic("unhandled error event") 127 | } 128 | }) 129 | return ch 130 | } 131 | func (g *FuncGroup) OneTimeEvent(event string, fnc func(Value)) { 132 | var cb Func 133 | fired := false 134 | cb = NewEventCallback(func(v Value) { 135 | if fired { 136 | panic("one time callback fired twice") 137 | } 138 | fired = true 139 | fnc(v) 140 | g.removeEventListener(event, cb) 141 | cb.Release() 142 | }) 143 | g.addEventListener(event, cb) 144 | g.Add(cb) 145 | } 146 | func (g *FuncGroup) OneTimeEventChan(event string) <-chan Value { 147 | ch := make(chan Value, 1) 148 | g.OneTimeEvent(event, func(v Value) { 149 | select { 150 | case ch <- v: 151 | default: 152 | panic("one time callback fired twice") 153 | } 154 | }) 155 | return ch 156 | } 157 | func (g *FuncGroup) OneTimeTrigger(event string) <-chan struct{} { 158 | ch := make(chan struct{}) 159 | g.OneTimeEvent(event, func(v Value) { 160 | close(ch) 161 | }) 162 | return ch 163 | } 164 | func (g *FuncGroup) Release() { 165 | for _, f := range g.funcs { 166 | f.Release() 167 | } 168 | g.funcs = nil 169 | } 170 | -------------------------------------------------------------------------------- /net/ws/wsconn_js.go: -------------------------------------------------------------------------------- 1 | //+build js 2 | 3 | package ws 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | "sync" 13 | "time" 14 | 15 | "github.com/dennwc/dom/js" 16 | ) 17 | 18 | var errClosed = errors.New("ws: connection closed") 19 | 20 | // Dial connects to a WebSocket on a specified URL. 21 | func Dial(addr string) (net.Conn, error) { 22 | return DialContext(context.Background(), addr) 23 | } 24 | 25 | // DialContext connects to a WebSocket on a specified URL. 26 | func DialContext(ctx context.Context, addr string) (_ net.Conn, gerr error) { 27 | c := &jsConn{ 28 | events: make(chan event, 2), 29 | done: make(chan struct{}), 30 | read: make(chan struct{}), 31 | } 32 | defer func() { 33 | if r := recover(); r != nil { 34 | if e, ok := r.(error); ok { 35 | gerr = e 36 | } else { 37 | gerr = fmt.Errorf("%v", r) 38 | } 39 | } 40 | }() 41 | p := c.openSocket(addr) 42 | 43 | out, err := p.AwaitContext(ctx) 44 | if err != nil { 45 | defer func() { 46 | c.state.Set("running", false) 47 | c.cb.Release() 48 | }() 49 | e := js.Value{err.(js.Error).Value} 50 | if !e.Get("message").IsUndefined() { 51 | return nil, fmt.Errorf("ws.dial: %v", err) 52 | } 53 | // after an error the connection should switch to a closed state 54 | ev := <-c.events 55 | if ev.Type == eventClosed { 56 | // unfortunately there is no way to get the real cause of an error 57 | code := ev.Data.Get("code").Int() 58 | return nil, fmt.Errorf("ws.dial: connection closed with code %d", code) 59 | } 60 | return nil, fmt.Errorf("ws.dial: connection failed, see console") 61 | } 62 | c.ws = out[0] 63 | // connected - start event loop 64 | go c.loop() 65 | return c, nil 66 | } 67 | 68 | type jsConn struct { 69 | ws js.Value 70 | cb js.Func 71 | state js.Value 72 | 73 | events chan event 74 | done chan struct{} 75 | read chan struct{} 76 | 77 | mu sync.Mutex 78 | err error 79 | rbuf bytes.Buffer 80 | } 81 | 82 | type event struct { 83 | Type eventType 84 | Data js.Value 85 | } 86 | 87 | type eventType int 88 | 89 | const ( 90 | eventError = eventType(0) 91 | eventOpened = eventType(1) 92 | eventClosed = eventType(2) 93 | eventData = eventType(3) 94 | ) 95 | 96 | func (c *jsConn) openSocket(addr string) *js.Promise { 97 | c.cb = js.CallbackOf(func(v []js.Value) { 98 | ev := event{ 99 | Type: eventType(v[0].Int()), 100 | Data: v[1], 101 | } 102 | select { 103 | case c.events <- ev: 104 | case <-c.done: 105 | } 106 | }) 107 | c.state = js.ValueOf(js.Obj{ 108 | "running": true, 109 | }) 110 | setup := js.NativeFuncOf("addr", "event", "state", ` 111 | return new Promise(function(resolve, reject){ 112 | var resolved = false; 113 | var s = new WebSocket(addr); 114 | s.binaryType = 'arraybuffer'; 115 | s.onerror = (e) => { 116 | if (!resolved) { 117 | resolved = true; 118 | reject(e); 119 | return; 120 | } 121 | event(0, e); 122 | } 123 | s.onopen = (e) => { 124 | if (!resolved) { 125 | resolved = true; 126 | resolve(s); 127 | return; 128 | } 129 | event(1, e); 130 | } 131 | s.onclose = (e) => { 132 | if (!state.running) return; 133 | event(2, e); 134 | } 135 | s.onmessage = (m) => { 136 | event(3, new Uint8Array(m.data)); 137 | } 138 | }) 139 | `) 140 | return setup.Invoke(addr, c.cb, c.state).Promised() 141 | } 142 | 143 | func (c *jsConn) Close() error { 144 | select { 145 | case <-c.done: 146 | default: 147 | close(c.done) 148 | } 149 | c.state.Set("running", false) 150 | c.ws.Call("close") 151 | c.cb.Release() 152 | return c.err 153 | } 154 | 155 | func (c *jsConn) wakeRead() { 156 | select { 157 | case c.read <- struct{}{}: 158 | default: 159 | } 160 | } 161 | 162 | func (c *jsConn) loop() { 163 | defer c.Close() 164 | for { 165 | select { 166 | case <-c.done: 167 | return 168 | case ev := <-c.events: 169 | switch ev.Type { 170 | case eventClosed: 171 | c.mu.Lock() 172 | c.err = errClosed 173 | c.mu.Unlock() 174 | c.wakeRead() 175 | return 176 | case eventError: 177 | c.mu.Lock() 178 | c.err = js.NewError(ev.Data) 179 | c.mu.Unlock() 180 | c.wakeRead() 181 | return 182 | case eventData: 183 | arr := ev.Data 184 | 185 | sz := arr.Get("length").Int() 186 | 187 | data := make([]byte, sz) 188 | m := js.MMap(data) 189 | err := m.CopyFrom(arr) 190 | m.Release() 191 | 192 | c.mu.Lock() 193 | if err == nil { 194 | c.rbuf.Write(data) 195 | } else { 196 | c.err = err 197 | } 198 | c.mu.Unlock() 199 | c.wakeRead() 200 | if err != nil { 201 | return 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | func cloneToJS(data []byte) js.Value { 209 | arr := js.TypedArrayOf(data) 210 | v := js.New("Uint8Array", arr) 211 | arr.Release() 212 | return v 213 | } 214 | 215 | func (c *jsConn) send(data []byte) { 216 | jarr := cloneToJS(data) 217 | c.ws.Call("send", jarr) 218 | } 219 | 220 | func (c *jsConn) Read(b []byte) (int, error) { 221 | for { 222 | var ( 223 | n int 224 | err error 225 | ) 226 | c.mu.Lock() 227 | if c.rbuf.Len() != 0 { 228 | n, err = c.rbuf.Read(b) 229 | } else { 230 | err = c.err 231 | } 232 | c.mu.Unlock() 233 | if err != nil || n != 0 { 234 | return n, err 235 | } 236 | select { 237 | case <-c.done: 238 | return 0, io.EOF 239 | case <-c.read: 240 | } 241 | } 242 | } 243 | 244 | func (c *jsConn) Write(b []byte) (int, error) { 245 | c.mu.Lock() 246 | err := c.err 247 | c.mu.Unlock() 248 | if err != nil { 249 | return 0, err 250 | } 251 | c.send(b) 252 | return len(b), nil 253 | } 254 | 255 | func (c *jsConn) LocalAddr() net.Addr { 256 | return wsAddr{} 257 | } 258 | 259 | func (c *jsConn) RemoteAddr() net.Addr { 260 | return wsAddr{} 261 | } 262 | 263 | func (c *jsConn) SetDeadline(t time.Time) error { 264 | return nil // TODO 265 | } 266 | 267 | func (c *jsConn) SetReadDeadline(t time.Time) error { 268 | return nil // TODO 269 | } 270 | 271 | func (c *jsConn) SetWriteDeadline(t time.Time) error { 272 | return nil // TODO 273 | } 274 | -------------------------------------------------------------------------------- /cmd/wasm-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "html/template" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "path" 12 | "path/filepath" 13 | "strings" 14 | "time" 15 | 16 | "github.com/dennwc/dom/internal/goenv" 17 | ) 18 | 19 | var ( 20 | host = flag.String("host", ":8080", "host to serve on") 21 | directory = flag.String("d", "./", "the directory of static file to host") 22 | cmds = flag.String("apps", "cmd", "the root directory for apps") 23 | def = flag.String("main", "app", "default app name") 24 | ) 25 | 26 | var ( 27 | indexTmpl = template.Must(template.New("index").Parse(indexHTML)) 28 | GOROOT string 29 | ) 30 | 31 | func main() { 32 | flag.Parse() 33 | 34 | GOROOT = goenv.GOROOT() 35 | 36 | h := http.FileServer(http.Dir(*directory)) 37 | 38 | http.Handle("/", buildHandler{h: h, dir: *directory}) 39 | 40 | fmt.Println("goroot: ", GOROOT) 41 | fmt.Println("apps folder: ", *cmds) 42 | fmt.Println("default app: ", *def) 43 | fmt.Println("static files:", *directory) 44 | fmt.Println("serving on: ", *host) 45 | err := http.ListenAndServe(*host, nil) 46 | if err != nil { 47 | fmt.Fprintln(os.Stderr, "error:", err) 48 | os.Exit(1) 49 | } 50 | } 51 | 52 | type buildHandler struct { 53 | h http.Handler 54 | dir string 55 | } 56 | 57 | func (h buildHandler) appPath(name string) string { 58 | return filepath.Join(h.dir, *cmds, name, "main.go") 59 | } 60 | 61 | func (h buildHandler) buildWASM(name string) { 62 | dst := filepath.Join(h.dir, name+".wasm") 63 | bin := "go" 64 | if GOROOT != "" { 65 | bin = filepath.Join(GOROOT, "bin", "go") 66 | } 67 | cmd := exec.Command(bin, "build", 68 | "-o", dst, 69 | h.appPath(name), 70 | ) 71 | cmd.Env = os.Environ() 72 | cmd.Env = append(cmd.Env, 73 | "GOOS=js", 74 | "GOARCH=wasm", 75 | ) 76 | if GOROOT != "" { 77 | cmd.Env = append(cmd.Env, 78 | "GOROOT="+GOROOT, 79 | ) 80 | } 81 | start := time.Now() 82 | out, err := cmd.CombinedOutput() 83 | dt := time.Since(start) 84 | if err != nil { 85 | os.Remove(dst) 86 | log.Println(err) 87 | log.Println("logs:\n" + string(out)) 88 | } else { 89 | log.Printf("built %q in %v", name, dt) 90 | } 91 | } 92 | 93 | func (h buildHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 94 | log.Println(r.Method, r.URL.Path) 95 | rpath := r.URL.Path 96 | if rpath == "/" { 97 | rpath = "/index.html" 98 | } 99 | switch rpath { 100 | case "/" + execName: 101 | http.ServeFile(w, r, filepath.Join(GOROOT, "misc", "wasm", execName)) 102 | return 103 | } 104 | switch ext := path.Ext(rpath); ext { 105 | case "", ".html": 106 | appName := *def 107 | if rpath != "/index.html" { 108 | appName = strings.Trim(strings.TrimSuffix(rpath, ext), "/") 109 | } 110 | if _, err := os.Stat(h.appPath(appName)); err == nil { 111 | err = indexTmpl.Execute(w, struct { 112 | AppName string 113 | }{ 114 | AppName: appName, 115 | }) 116 | if err != nil { 117 | log.Println(err) 118 | } 119 | return 120 | } 121 | case ".wasm": 122 | h.buildWASM(strings.TrimSuffix(r.URL.Path, ext)) 123 | } 124 | h.h.ServeHTTP(w, r) 125 | } 126 | 127 | const ( 128 | execName = "wasm_exec.js" 129 | ) 130 | 131 | const indexHTML = ` 132 | 137 | 138 | 139 | 140 | 141 | {{ .AppName }} 142 | 143 | 144 | 145 | 146 | 168 | 169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
Loading...
182 |
183 | 184 | 185 | 186 | ` 187 | 188 | const spinCSS = `.sk-cube-grid { 189 | width: 40px; 190 | height: 40px; 191 | margin: 100px auto 20px auto; 192 | } 193 | 194 | .sk-text { 195 | font-family: Arial, Helvetica, sans-serif; 196 | text-align: center; 197 | margin: 0 auto 30px auto; 198 | } 199 | 200 | .sk-cube-grid .sk-cube { 201 | width: 33%; 202 | height: 33%; 203 | background-color: #333; 204 | float: left; 205 | -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; 206 | animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; 207 | } 208 | .sk-cube-grid .sk-cube1 { 209 | -webkit-animation-delay: 0.2s; 210 | animation-delay: 0.2s; } 211 | .sk-cube-grid .sk-cube2 { 212 | -webkit-animation-delay: 0.3s; 213 | animation-delay: 0.3s; } 214 | .sk-cube-grid .sk-cube3 { 215 | -webkit-animation-delay: 0.4s; 216 | animation-delay: 0.4s; } 217 | .sk-cube-grid .sk-cube4 { 218 | -webkit-animation-delay: 0.1s; 219 | animation-delay: 0.1s; } 220 | .sk-cube-grid .sk-cube5 { 221 | -webkit-animation-delay: 0.2s; 222 | animation-delay: 0.2s; } 223 | .sk-cube-grid .sk-cube6 { 224 | -webkit-animation-delay: 0.3s; 225 | animation-delay: 0.3s; } 226 | .sk-cube-grid .sk-cube7 { 227 | -webkit-animation-delay: 0s; 228 | animation-delay: 0s; } 229 | .sk-cube-grid .sk-cube8 { 230 | -webkit-animation-delay: 0.1s; 231 | animation-delay: 0.1s; } 232 | .sk-cube-grid .sk-cube9 { 233 | -webkit-animation-delay: 0.2s; 234 | animation-delay: 0.2s; } 235 | 236 | @-webkit-keyframes sk-cubeGridScaleDelay { 237 | 0%, 70%, 100% { 238 | -webkit-transform: scale3D(1, 1, 1); 239 | transform: scale3D(1, 1, 1); 240 | } 35% { 241 | -webkit-transform: scale3D(0, 0, 1); 242 | transform: scale3D(0, 0, 1); 243 | } 244 | } 245 | 246 | @keyframes sk-cubeGridScaleDelay { 247 | 0%, 70%, 100% { 248 | -webkit-transform: scale3D(1, 1, 1); 249 | transform: scale3D(1, 1, 1); 250 | } 35% { 251 | -webkit-transform: scale3D(0, 0, 1); 252 | transform: scale3D(0, 0, 1); 253 | } 254 | }` 255 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= 2 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 3 | github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= 4 | github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= 5 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= 6 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= 7 | github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY= 8 | github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 9 | github.com/chromedp/cdproto v0.0.0-20180713053126-e314dc107013/go.mod h1:C2GPAraqdt1KfZU7aSmx1XUgarNq/3JmxevQkmCjOVs= 10 | github.com/chromedp/cdproto v0.0.0-20190217000753-2d8e8962ceb2 h1:4Ck8YOuS0G3+0xMb80cDSff7QpUolhSc0PGyfagbcdA= 11 | github.com/chromedp/cdproto v0.0.0-20190217000753-2d8e8962ceb2/go.mod h1:xquOK9dIGFlLaIGI4c6IyfLI/Gz0LiYYuJtzhsUODgI= 12 | github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac h1:PThQaO4yCvJzJBUW1XoFQxLotWRhvX2fgljJX8yrhFI= 13 | github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/dennwc/chromedp v0.1.3-0.20181116212057-ac5b86a6b983 h1:MUllIv/nz+HJiOnVbMbjcF0Gf4iBefDfQFGXv/tS1B0= 17 | github.com/dennwc/chromedp v0.1.3-0.20181116212057-ac5b86a6b983/go.mod h1:83UDY5CKmHrvKLQ6vVU+LVFUcfjOSPNufx8XFWLUYlQ= 18 | github.com/dennwc/testproxy v1.0.1 h1:mQhNVWHPolTYjJrDZYKcugIplWRSlFAis6k/Zf1s0c0= 19 | github.com/dennwc/testproxy v1.0.1/go.mod h1:EHGV9tzWhMPLmEoVJ2KGyC149XqwKZwBDViCjhKD5d8= 20 | github.com/disintegration/imaging v1.4.2 h1:BSVxoYQ2NfLdvIGCDD8GHgBV5K0FCEsc0d/6FxQII3I= 21 | github.com/disintegration/imaging v1.4.2/go.mod h1:9B/deIUIrliYkyMTuXJd6OUFLcrZ2tf+3Qlwnaf/CjU= 22 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 23 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 24 | github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= 25 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 26 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 27 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 28 | github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 29 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 30 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 31 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= 32 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= 33 | github.com/knq/sysutil v0.0.0-20180306023629-0218e141a794/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= 34 | github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307 h1:vl4eIlySbjertFaNwiMjXsGrFVK25aOWLq7n+3gh2ls= 35 | github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= 36 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 37 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 38 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 39 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 40 | github.com/mailru/easyjson v0.0.0-20180606163543-3fdea8d05856/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 41 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= 42 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 43 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 44 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 45 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= 46 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 47 | github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= 48 | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 49 | github.com/ory/dockertest v3.3.2+incompatible h1:uO+NcwH6GuFof/Uz8yzjNi1g0sGT5SLAJbdBvD8bUYc= 50 | github.com/ory/dockertest v3.3.2+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= 51 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 52 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 56 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 57 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 58 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 59 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 60 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= 61 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 62 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= 63 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 64 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= 65 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 66 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= 67 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 68 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 69 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU= 70 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 71 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 72 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 73 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 74 | -------------------------------------------------------------------------------- /js/js.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | object = global.Get("Object") 10 | array = global.Get("Array") 11 | ) 12 | 13 | var ( 14 | mu sync.RWMutex 15 | classes = make(map[string]Value) 16 | ) 17 | 18 | // Obj is an alias for map[string]interface{}. 19 | type Obj = map[string]interface{} 20 | 21 | // Arr is an alias for []interface{}. 22 | type Arr = []interface{} 23 | 24 | // Get is a shorthand for Global().Get(). 25 | func Get(name string, path ...string) Value { 26 | return Value{global}.Get(name, path...) 27 | } 28 | 29 | // Set is a shorthand for Global().Set(). 30 | func Set(name string, v interface{}) { 31 | Value{global}.Set(name, v) 32 | } 33 | 34 | // Call is a shorthand for Global().Call(). 35 | func Call(name string, args ...interface{}) Value { 36 | return Value{global}.Call(name, args...) 37 | } 38 | 39 | // Class searches for a class in global scope. 40 | // It caches results, so the lookup should be faster than calling Get. 41 | func Class(class string, path ...string) Value { 42 | switch class { 43 | case "Object": 44 | return Value{object} 45 | case "Array": 46 | return Value{array} 47 | } 48 | key := class 49 | if len(path) != 0 { 50 | key += "." + strings.Join(path, ".") 51 | } 52 | mu.RLock() 53 | v := classes[key] 54 | mu.RUnlock() 55 | if v.isZero() { 56 | v = Get(class, path...) 57 | mu.Lock() 58 | classes[key] = v 59 | mu.Unlock() 60 | } 61 | return v 62 | } 63 | 64 | // New searches for a class in global scope and creates a new instance of that class. 65 | func New(class string, args ...interface{}) Value { 66 | v := Class(class) 67 | return v.New(args...) 68 | } 69 | 70 | // NewError creates a new Go error from JS error value. 71 | func NewError(e Wrapper) error { 72 | return Error{Value: e.JSValue()} 73 | } 74 | 75 | // Object returns an Object JS class. 76 | func Object() Value { 77 | return Value{object} 78 | } 79 | 80 | // Array returns an Array JS class. 81 | func Array() Value { 82 | return Value{array} 83 | } 84 | 85 | // NewObject creates an empty JS object. 86 | func NewObject() Value { 87 | return Object().New() 88 | } 89 | 90 | // NewArray creates an empty JS array. 91 | func NewArray() Value { 92 | return Array().New() 93 | } 94 | 95 | func toJS(o interface{}) interface{} { 96 | switch v := o.(type) { 97 | case []Value: 98 | refs := make([]interface{}, 0, len(v)) 99 | for _, ref := range v { 100 | refs = append(refs, ref.JSValue()) 101 | } 102 | o = refs 103 | case []Ref: 104 | refs := make([]interface{}, 0, len(v)) 105 | for _, ref := range v { 106 | refs = append(refs, ref) 107 | } 108 | o = refs 109 | } 110 | return o 111 | } 112 | 113 | var _ Wrapper = Value{} 114 | 115 | // Value is a convenience wrapper for syscall/js.Value. 116 | // It provides some additional functionality, while storing no additional state. 117 | // Its safe to instantiate Value directly, by wrapping syscall/js.Value. 118 | type Value struct { 119 | Ref 120 | } 121 | 122 | func (v Value) isZero() bool { 123 | return v == (Value{}) 124 | } 125 | 126 | // JSValue implements Wrapper interface. 127 | func (v Value) JSValue() Ref { 128 | return v.Ref 129 | } 130 | 131 | // String converts a value to a string. 132 | func (v Value) String() string { 133 | if !v.Valid() { 134 | return "" 135 | } 136 | return v.Ref.String() 137 | } 138 | 139 | // IsNull checks if a value represents JS null object. 140 | func (v Value) IsNull() bool { 141 | return v.Ref == null 142 | } 143 | 144 | // IsUndefined checks if a value represents JS undefined object. 145 | func (v Value) IsUndefined() bool { 146 | return v.Ref == undefined 147 | } 148 | 149 | // Valid checks if object is defined and not null. 150 | func (v Value) Valid() bool { 151 | return !v.isZero() && !v.IsNull() && !v.IsUndefined() 152 | } 153 | 154 | // Get returns the JS property by name. 155 | func (v Value) Get(name string, path ...string) Value { 156 | ref := v.Ref.Get(name) 157 | for _, p := range path { 158 | ref = ref.Get(p) 159 | } 160 | return Value{ref} 161 | } 162 | 163 | // Set sets the JS property to ValueOf(x). 164 | func (v Value) Set(name string, val interface{}) { 165 | v.Ref.Set(name, valueOf(val)) 166 | } 167 | 168 | // TODO: Del 169 | 170 | // Index returns JS index i of value v. 171 | func (v Value) Index(i int) Value { 172 | return Value{v.Ref.Index(i)} 173 | } 174 | 175 | // SetIndex sets the JavaScript index i of value v to ValueOf(x). 176 | func (v Value) SetIndex(i int, val interface{}) { 177 | v.Ref.SetIndex(i, valueOf(val)) 178 | } 179 | 180 | // Call does a JavaScript call to the method m of value v with the given arguments. 181 | // It panics if v has no method m. 182 | // The arguments get mapped to JavaScript values according to the ValueOf function. 183 | func (v Value) Call(name string, args ...interface{}) Value { 184 | for i, a := range args { 185 | args[i] = valueOf(a) 186 | } 187 | return Value{v.Ref.Call(name, args...)} 188 | } 189 | 190 | // Invoke does a JavaScript call of the value v with the given arguments. 191 | // It panics if v is not a function. 192 | // The arguments get mapped to JavaScript values according to the ValueOf function. 193 | func (v Value) Invoke(args ...interface{}) Value { 194 | for i, a := range args { 195 | args[i] = valueOf(a) 196 | } 197 | return Value{v.Ref.Invoke(args...)} 198 | } 199 | 200 | // New uses JavaScript's "new" operator with value v as constructor and the given arguments. 201 | // It panics if v is not a function. 202 | // The arguments get mapped to JavaScript values according to the ValueOf function. 203 | func (v Value) New(args ...interface{}) Value { 204 | for i, a := range args { 205 | args[i] = valueOf(a) 206 | } 207 | return Value{v.Ref.New(args...)} 208 | } 209 | 210 | // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator. 211 | func (v Value) InstanceOf(class Wrapper) bool { 212 | return v.Ref.InstanceOf(class.JSValue()) 213 | } 214 | 215 | // InstanceOfClass reports whether v is an instance of named type according to JavaScript's instanceof operator. 216 | func (v Value) InstanceOfClass(class string) bool { 217 | return v.InstanceOf(Class(class)) 218 | } 219 | 220 | // Slice converts JS Array to a Go slice of JS values. 221 | func (v Value) Slice() []Value { 222 | if !v.Valid() { 223 | return nil 224 | } 225 | n := v.Length() 226 | vals := make([]Value, 0, n) 227 | for i := 0; i < n; i++ { 228 | vals = append(vals, v.Index(i)) 229 | } 230 | return vals 231 | } 232 | 233 | // ValueOf returns x as a JavaScript value: 234 | // 235 | // | Go | JavaScript | 236 | // | ---------------------- | ---------------------- | 237 | // | js.Value | [its value] | 238 | // | js.TypedArray | typed array | 239 | // | js.Callback | function | 240 | // | nil | null | 241 | // | bool | boolean | 242 | // | integers and floats | number | 243 | // | string | string | 244 | // | []interface{} | new array | 245 | // | map[string]interface{} | new object | 246 | func ValueOf(o interface{}) Value { 247 | return Value{valueOf(o)} 248 | } 249 | -------------------------------------------------------------------------------- /examples/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= 3 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 4 | github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= 5 | github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= 6 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= 7 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= 8 | github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY= 9 | github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 10 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 11 | github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac h1:PThQaO4yCvJzJBUW1XoFQxLotWRhvX2fgljJX8yrhFI= 12 | github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/dennwc/dom v0.2.1 h1:ADY36S5pLkfm+2l6mWj5XHtt/InVXZROijUG36+lMuY= 16 | github.com/dennwc/dom v0.2.1/go.mod h1:oFVwHbm2UiC3fpTHdNYk78qvsuvdJFnPWSBonoYGdYs= 17 | github.com/dennwc/testproxy v1.0.1 h1:mQhNVWHPolTYjJrDZYKcugIplWRSlFAis6k/Zf1s0c0= 18 | github.com/dennwc/testproxy v1.0.1/go.mod h1:EHGV9tzWhMPLmEoVJ2KGyC149XqwKZwBDViCjhKD5d8= 19 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 20 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 21 | github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= 22 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 23 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 24 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 25 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 26 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 27 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 28 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 29 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 30 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 31 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 32 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 33 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 34 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 35 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:1yOKgt0XYKUg1HOKunGOSt2ocU4bxLCjmIHt0vRtVHM= 36 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= 37 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 38 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 39 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 40 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 41 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 42 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 43 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 44 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= 45 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 46 | github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= 47 | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 48 | github.com/ory/dockertest v3.3.2+incompatible h1:uO+NcwH6GuFof/Uz8yzjNi1g0sGT5SLAJbdBvD8bUYc= 49 | github.com/ory/dockertest v3.3.2+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= 50 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 51 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 52 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 54 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 55 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 56 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 57 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 58 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 59 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= 60 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 61 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 62 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 63 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= 64 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 65 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 66 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 67 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 68 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= 69 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 70 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 71 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU= 72 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 73 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 74 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 75 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 76 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 77 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= 78 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 79 | google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= 80 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 81 | gotest.tools v2.2.0+incompatible h1:y0IMTfclpMdsdIbr6uwmJn5/WZ7vFuObxDMdrylFM3A= 82 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 83 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 84 | -------------------------------------------------------------------------------- /net/webrtc/peer_conn.go: -------------------------------------------------------------------------------- 1 | //+build js,wasm 2 | 3 | package webrtc 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "sync" 9 | "sync/atomic" 10 | 11 | "github.com/dennwc/dom" 12 | "github.com/dennwc/dom/js" 13 | ) 14 | 15 | const debug = false 16 | 17 | var lastID uint32 18 | 19 | func newPeerConnection() *peerConnection { 20 | p := &peerConnection{ 21 | id: atomic.AddUint32(&lastID, 1), 22 | v: js.New("RTCPeerConnection"), 23 | 24 | errc: make(chan error, 1), 25 | newChan: make(chan string, 1), 26 | chans: make(map[string]*peerChannel), 27 | } 28 | p.registerStateHandlers() 29 | return p 30 | } 31 | 32 | type peerConnection struct { 33 | id uint32 34 | v js.Value 35 | g *js.FuncGroup 36 | 37 | errc chan error 38 | newChan chan string 39 | cmu sync.Mutex 40 | chans map[string]*peerChannel 41 | } 42 | 43 | func (c *peerConnection) Channel(name string) *peerChannel { 44 | c.cmu.Lock() 45 | defer c.cmu.Unlock() 46 | return c.chans[name] 47 | } 48 | 49 | func (c *peerConnection) WaitChannel(name string) (*peerChannel, error) { 50 | ch := c.Channel(name) 51 | if ch == nil { 52 | // wait for the channel to appear 53 | select { 54 | case err := <-c.errc: 55 | c.Close() 56 | return nil, err 57 | case cname := <-c.newChan: 58 | if cname != name { 59 | c.Close() 60 | return nil, fmt.Errorf("unexpected channel: %q", cname) 61 | } 62 | ch = c.Channel(name) 63 | } 64 | } 65 | select { 66 | case <-ch.done: 67 | return nil, errors.New("webrtc: channel closed") 68 | case <-ch.ready: 69 | return ch, nil 70 | } 71 | } 72 | 73 | func (c *peerConnection) newChannel(name string, ch js.Value, ready bool) { 74 | peer := &peerChannel{ 75 | c: c, name: name, v: ch, 76 | ready: make(chan struct{}), 77 | done: make(chan struct{}), 78 | read: make(chan struct{}), 79 | } 80 | if ready { 81 | close(peer.ready) 82 | } 83 | c.cmu.Lock() 84 | defer c.cmu.Unlock() 85 | c.chans[name] = peer 86 | // keep only the last notification on the channel 87 | select { 88 | case c.newChan <- name: 89 | default: 90 | // remove value from the channel and retry 91 | select { 92 | case <-c.newChan: 93 | default: 94 | // holding mutex, we are the only who can send 95 | c.newChan <- name 96 | } 97 | } 98 | } 99 | 100 | type eventType int 101 | 102 | const ( 103 | eventError = eventType(0) 104 | eventNew = eventType(1) 105 | eventOpened = eventType(2) 106 | eventClosed = eventType(3) 107 | eventMessage = eventType(4) 108 | ) 109 | 110 | type chanEvent struct { 111 | Name string 112 | Type eventType 113 | Data js.Value 114 | } 115 | 116 | func (c *peerConnection) registerStateHandlers() { 117 | c.g = c.v.NewFuncGroup() 118 | // always register an error and connection state event handlers 119 | c.g.Set("onerror", func(v []js.Value) { 120 | dom.ConsoleLog("error:", c.id, v[0]) 121 | select { 122 | case c.errc <- js.NewError(v[0]): 123 | default: 124 | } 125 | }) 126 | if debug { 127 | c.g.Set("oniceconnectionstatechange", func(v []js.Value) { 128 | dom.ConsoleLog("state:", c.id, c.v.Get("iceConnectionState")) 129 | }) 130 | c.g.Set("onsignalingstatechange", func(v []js.Value) { 131 | dom.ConsoleLog("sig state:", c.id, c.v.Get("signalingState")) 132 | }) 133 | c.g.Set("onicegatheringstatechange", func(v []js.Value) { 134 | dom.ConsoleLog("gather state:", c.id, c.v.Get("iceGatheringState")) 135 | }) 136 | } 137 | 138 | // handle incoming data channels 139 | jfnc := js.JSFuncOf("fnc", ` 140 | return function(ce) { 141 | const ch = ce.channel; 142 | const name = ch.label; 143 | 144 | fnc(name, 1, ch); 145 | ch.onerror = (e) => { 146 | fnc(name, 0, e); 147 | } 148 | ch.onopen = (e) => { 149 | fnc(name, 2, e); 150 | } 151 | ch.onclose = (e) => { 152 | fnc(name, 3, e); 153 | } 154 | ch.onmessage = (e) => { 155 | fnc(name, 4, e.data); 156 | } 157 | } 158 | `) 159 | cb := c.newChanEventCallback() 160 | c.v.Set("ondatachannel", jfnc.Invoke(cb)) 161 | } 162 | 163 | func (c *peerConnection) Close() error { 164 | c.v.Call("close") 165 | c.g.Release() 166 | return nil 167 | } 168 | 169 | func (c *peerConnection) onICECandidate(fnc func(cand js.Value)) js.Func { 170 | cb := js.CallbackOf(func(v []js.Value) { 171 | cand := v[0].Get("candidate") 172 | if debug { 173 | dom.ConsoleLog("candidate:", c.id, cand) 174 | } 175 | fnc(cand) 176 | }) 177 | c.v.Set("onicecandidate", cb) 178 | return cb 179 | } 180 | 181 | func (c *peerConnection) OnICECandidate(fnc func(cand js.Value)) { 182 | cb := c.onICECandidate(fnc) 183 | c.g.Add(cb) 184 | } 185 | 186 | func (c *peerConnection) newChanEventCallback() js.Func { 187 | cb := js.CallbackOf(func(v []js.Value) { 188 | e := chanEvent{ 189 | Name: v[0].String(), 190 | Type: eventType(v[1].Int()), 191 | Data: v[2], 192 | } 193 | 194 | if debug { 195 | dom.ConsoleLog("chan:", c.id, e.Name, e.Type, e.Data) 196 | } 197 | switch e.Type { 198 | case eventNew: 199 | c.newChannel(e.Name, e.Data, false) 200 | default: 201 | ch := c.Channel(e.Name) 202 | if ch != nil { 203 | ch.handleEvent(e) 204 | } 205 | } 206 | }) 207 | c.g.Add(cb) 208 | return cb 209 | } 210 | 211 | func (c *peerConnection) NewDataChannel(name string) { 212 | // TODO: it can be the same callback 213 | cb := c.newChanEventCallback() 214 | // handle initiated data channels 215 | ch := js.NativeFuncOf("v", "name", "fnc", ` 216 | const ch = v.createDataChannel(name); 217 | ch.onerror = (e) => { 218 | fnc(name, 0, e); 219 | } 220 | ch.onopen = (e) => { 221 | fnc(name, 2, e); 222 | } 223 | ch.onclose = (e) => { 224 | fnc(name, 3, e); 225 | } 226 | ch.onmessage = (e) => { 227 | fnc(name, 4, e.data); 228 | } 229 | return ch; 230 | `).Invoke(c.v, name, cb) 231 | 232 | // new channels are never reported as "opened" 233 | // but if they were added before connection was established, they do 234 | c.newChannel(name, ch, false) 235 | } 236 | 237 | func (c *peerConnection) AddICECandidate(v js.Value) error { 238 | if debug { 239 | dom.ConsoleLog("add candidate:", c.id, v) 240 | } 241 | _, err := c.v.Call("addIceCandidate", v).Await() 242 | return err 243 | } 244 | 245 | func (c *peerConnection) SetLocalDescription(d js.Value) error { 246 | if debug { 247 | dom.ConsoleLog("set local desc:", c.id, d) 248 | } 249 | _, err := c.v.Call("setLocalDescription", d).Await() 250 | return err 251 | } 252 | 253 | func (c *peerConnection) CreateOffer() (js.Value, error) { 254 | vals, err := c.v.Call("createOffer").Await() 255 | if err != nil { 256 | return js.Value{}, err 257 | } 258 | offer := vals[0] 259 | return offer, nil 260 | } 261 | 262 | func (c *peerConnection) SetRemoteDescription(v js.Value) error { 263 | if debug { 264 | dom.ConsoleLog("set remote desc:", c.id, v) 265 | } 266 | _, err := c.v.Call("setRemoteDescription", v).Await() 267 | return err 268 | } 269 | 270 | func (c *peerConnection) CreateAnswer() (js.Value, error) { 271 | vals, err := c.v.Call("createAnswer").Await() 272 | if err != nil { 273 | return js.Value{}, err 274 | } 275 | answer := vals[0] 276 | return answer, nil 277 | } 278 | 279 | type iceFunc func() ([]js.Value, error) 280 | 281 | func (c *peerConnection) CollectICEs() iceFunc { 282 | var ( 283 | cb js.Func 284 | done = make(chan struct{}) 285 | ices []js.Value 286 | ) 287 | cb = c.onICECandidate(func(cand js.Value) { 288 | if !cand.Valid() { 289 | close(done) 290 | cb.Release() 291 | return 292 | } 293 | ices = append(ices, cand) 294 | }) 295 | return func() ([]js.Value, error) { 296 | <-done // TODO: listen on some kind of error channel 297 | if len(ices) == 0 { 298 | return nil, fmt.Errorf("no ICE candidates collected") 299 | } 300 | return ices, nil 301 | } 302 | } 303 | 304 | func (c *peerConnection) SetICECandidates(ices []js.Value) error { 305 | for _, ice := range ices { 306 | if err := c.AddICECandidate(ice); err != nil { 307 | return err 308 | } 309 | } 310 | // signal "no more ICEs" 311 | // TODO: docs says it's should be sent, but this call fails 312 | //if err := c.AddICECandidate(js.ValueOf("")); err != nil { 313 | // return err 314 | //} 315 | return nil 316 | } 317 | -------------------------------------------------------------------------------- /svg/svg.go: -------------------------------------------------------------------------------- 1 | package svg 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/dennwc/dom" 8 | "github.com/dennwc/dom/js" 9 | ) 10 | 11 | // NewElement creates a new SVG element. 12 | func NewElement(tag string) *Element { 13 | return &Element{dom.Doc.CreateElementNS("http://www.w3.org/2000/svg", tag)} 14 | } 15 | 16 | // NewContainer creates an SVG element that provides container-like API (like "g"). 17 | func NewContainer(tag string) *Container { 18 | return &Container{*NewElement(tag)} 19 | } 20 | 21 | // New creates a new root SVG element with a given size. 22 | func New(w, h dom.Unit) *SVG { 23 | e := NewContainer("svg") 24 | dom.Body.AppendChild(e.DOMElement()) 25 | e.SetAttribute("width", w.String()) 26 | e.SetAttribute("height", h.String()) 27 | return &SVG{*e} 28 | } 29 | 30 | // NewFullscreen is like New, but the resulting element will try to fill the whole client area. 31 | func NewFullscreen() *SVG { 32 | return New(dom.Perc(100), dom.Vh(98)) 33 | } 34 | 35 | var _ js.Wrapper = (*Element)(nil) 36 | 37 | // JSValue implements js.Wrapper. 38 | func (e *Element) JSValue() js.Ref { 39 | return e.e.JSValue() 40 | } 41 | 42 | // Element is a common base for SVG elements. 43 | type Element struct { 44 | e *dom.Element 45 | } 46 | 47 | // SetAttribute sets an attribute of SVG element. 48 | func (e *Element) SetAttribute(k string, v interface{}) { 49 | e.e.SetAttribute(k, v) 50 | } 51 | 52 | // Style returns a style object for this element. 53 | func (e *Element) Style() *dom.Style { 54 | return dom.AsStyle(js.Value{Ref: e.JSValue().Get("style")}) 55 | } 56 | 57 | // Transform sets a list of transformations for SVG element. 58 | // It will override an old value. 59 | func (e *Element) Transform(arr ...Transform) { 60 | str := make([]string, 0, len(arr)) 61 | for _, t := range arr { 62 | str = append(str, t.TransformString()) 63 | } 64 | e.e.SetAttribute("transform", strings.Join(str, " ")) 65 | } 66 | 67 | // Translate sets an SVG element transform to translation. 68 | // It will override an old transform value. 69 | func (e *Element) Translate(x, y float64) { 70 | e.Transform(Translate{X: x, Y: y}) 71 | } 72 | 73 | // OnClick registers an onclick event listener. 74 | func (e *Element) OnClick(h dom.MouseEventHandler) { 75 | e.e.OnClick(h) 76 | } 77 | 78 | // OnMouseDown registers an onmousedown event listener. 79 | func (e *Element) OnMouseDown(h dom.MouseEventHandler) { 80 | e.e.OnMouseDown(h) 81 | } 82 | 83 | // OnMouseMove registers an onmousemove event listener. 84 | func (e *Element) OnMouseMove(h dom.MouseEventHandler) { 85 | e.e.OnMouseMove(h) 86 | } 87 | 88 | // OnMouseUp registers an onmouseup event listener. 89 | func (e *Element) OnMouseUp(h dom.MouseEventHandler) { 90 | e.e.OnMouseUp(h) 91 | } 92 | 93 | // NewG creates a detached SVG group element ("g"). 94 | func NewG() *G { 95 | return &G{*NewContainer("g")} 96 | } 97 | 98 | // NewCircle creates a detached SVG circle with a given radius. 99 | func NewCircle(r int) *Circle { 100 | c := &Circle{*NewElement("circle")} 101 | c.SetR(r) 102 | return c 103 | } 104 | 105 | // NewRect creates a detached SVG rectangle with a given size. 106 | func NewRect(w, h int) *Rect { 107 | r := &Rect{*NewElement("rect")} 108 | if w != 0 || h != 0 { 109 | r.SetSize(w, h) 110 | } 111 | return r 112 | } 113 | 114 | // NewLine creates a detached SVG line. 115 | func NewLine() *Line { 116 | l := &Line{*NewElement("line")} 117 | l.SetStrokeWidth(1) 118 | l.SetAttribute("stroke", "#000") 119 | return l 120 | } 121 | 122 | // NewText creates a detached SVG text element. 123 | func NewText(str string) *Text { 124 | t := &Text{*NewElement("text")} 125 | t.SetText(str) 126 | return t 127 | } 128 | 129 | // Container is a common base for SVG elements that can contain other elements. 130 | type Container struct { 131 | Element 132 | } 133 | 134 | // NewG creates an SVG group element ("g") in this container. 135 | func (c *Container) NewG() *G { 136 | g := NewG() 137 | c.e.AppendChild(g.DOMElement()) 138 | return g 139 | } 140 | 141 | // NewCircle creates an SVG circle with a given radius in this container. 142 | func (c *Container) NewCircle(r int) *Circle { 143 | ci := NewCircle(r) 144 | c.e.AppendChild(ci.DOMElement()) 145 | return ci 146 | } 147 | 148 | // NewRect creates an SVG rectangle with a given size in this container. 149 | func (c *Container) NewRect(w, h int) *Rect { 150 | r := NewRect(w, h) 151 | c.e.AppendChild(r.DOMElement()) 152 | return r 153 | } 154 | 155 | // NewLine creates an SVG line in this container. 156 | func (c *Container) NewLine() *Line { 157 | l := NewLine() 158 | c.e.AppendChild(l.DOMElement()) 159 | return l 160 | } 161 | 162 | // NewText creates an SVG text element in this container. 163 | func (c *Container) NewText(str string) *Text { 164 | t := NewText(str) 165 | c.e.AppendChild(t.DOMElement()) 166 | return t 167 | } 168 | 169 | // SVG is a root SVG element. 170 | type SVG struct { 171 | Container 172 | } 173 | 174 | // DOMElement returns a dom.Element associated with this SVG element. 175 | func (e *Element) DOMElement() *dom.Element { 176 | return e.e 177 | } 178 | 179 | // G is an SVG group element. 180 | type G struct { 181 | Container 182 | } 183 | 184 | // Circle is an SVG circle element. 185 | type Circle struct { 186 | Element 187 | } 188 | 189 | func (c *Circle) SetR(r int) { 190 | c.SetAttribute("r", r) 191 | } 192 | func (c *Circle) SetPos(x, y int) { 193 | c.SetAttribute("cx", x) 194 | c.SetAttribute("cy", y) 195 | } 196 | func (c *Circle) Fill(cl dom.Color) { 197 | c.SetAttribute("fill", string(cl)) 198 | } 199 | func (c *Circle) Stroke(cl dom.Color) { 200 | c.SetAttribute("stroke", string(cl)) 201 | } 202 | 203 | // Rect is an SVG rectangle element. 204 | type Rect struct { 205 | Element 206 | } 207 | 208 | func (c *Rect) SetPos(x, y int) { 209 | c.SetAttribute("x", x) 210 | c.SetAttribute("y", y) 211 | } 212 | func (c *Rect) SetSize(w, h int) { 213 | c.SetAttribute("width", w) 214 | c.SetAttribute("height", h) 215 | } 216 | func (c *Rect) SetRound(rx, ry int) { 217 | c.SetAttribute("rx", rx) 218 | c.SetAttribute("ry", ry) 219 | } 220 | func (c *Rect) Fill(cl dom.Color) { 221 | c.SetAttribute("fill", string(cl)) 222 | } 223 | func (c *Rect) Stroke(cl dom.Color) { 224 | c.SetAttribute("stroke", string(cl)) 225 | } 226 | 227 | // Line is an SVG line element. 228 | type Line struct { 229 | Element 230 | } 231 | 232 | func (l *Line) SetStrokeWidth(w float64) { 233 | l.SetAttribute("stroke-width", w) 234 | } 235 | func (l *Line) SetPos1(p dom.Point) { 236 | l.SetAttribute("x1", p.X) 237 | l.SetAttribute("y1", p.Y) 238 | } 239 | func (l *Line) SetPos2(p dom.Point) { 240 | l.SetAttribute("x2", p.X) 241 | l.SetAttribute("y2", p.Y) 242 | } 243 | func (l *Line) SetPos(p1, p2 dom.Point) { 244 | l.SetPos1(p1) 245 | l.SetPos2(p2) 246 | } 247 | 248 | // Text is an SVG text element. 249 | type Text struct { 250 | Element 251 | } 252 | 253 | func (t *Text) SetText(s string) { 254 | t.e.SetInnerHTML(s) 255 | } 256 | func (t *Text) SetPos(x, y int) { 257 | t.SetAttribute("x", x) 258 | t.SetAttribute("y", y) 259 | } 260 | func (t *Text) SetDPos(dx, dy dom.Unit) { 261 | if dx != nil { 262 | t.SetAttribute("dx", dx.String()) 263 | } 264 | if dy != nil { 265 | t.SetAttribute("dy", dy.String()) 266 | } 267 | } 268 | func (t *Text) Selectable(v bool) { 269 | if !v { 270 | t.Style().Set("user-select", "none") 271 | } else { 272 | t.Style().Set("user-select", "auto") 273 | } 274 | } 275 | 276 | // Transform is transformation that can be applied to SVG elements. 277 | type Transform interface { 278 | TransformString() string 279 | } 280 | 281 | // Translate moves an element. 282 | type Translate struct { 283 | X, Y float64 284 | } 285 | 286 | func (t Translate) TransformString() string { 287 | return fmt.Sprintf("translate(%v, %v)", t.X, t.Y) 288 | } 289 | 290 | // Scale scales an element. 291 | type Scale struct { 292 | X, Y float64 293 | } 294 | 295 | func (t Scale) TransformString() string { 296 | return fmt.Sprintf("scale(%v, %v)", t.X, t.Y) 297 | } 298 | 299 | // Rotate rotates an element relative to the parent. 300 | type Rotate struct { 301 | A float64 302 | } 303 | 304 | func (t Rotate) TransformString() string { 305 | return fmt.Sprintf("rotate(%v)", t.A) 306 | } 307 | 308 | // RotatePt rotates an element relative a point. 309 | type RotatePt struct { 310 | A, X, Y float64 311 | } 312 | 313 | func (t RotatePt) TransformString() string { 314 | return fmt.Sprintf("rotate(%v, %v, %v)", t.A, t.X, t.Y) 315 | } 316 | -------------------------------------------------------------------------------- /js/jstest/chrome.go: -------------------------------------------------------------------------------- 1 | //+build !js 2 | 3 | package jstest 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "io" 11 | "math/rand" 12 | "net" 13 | "net/http" 14 | "os" 15 | "path/filepath" 16 | "strconv" 17 | "strings" 18 | "testing" 19 | "time" 20 | 21 | clog "github.com/chromedp/cdproto/log" 22 | "github.com/chromedp/cdproto/runtime" 23 | "github.com/chromedp/chromedp" 24 | "github.com/chromedp/chromedp/client" 25 | "github.com/dennwc/dom/internal/goenv" 26 | "github.com/dennwc/testproxy" 27 | "github.com/ory/dockertest" 28 | "github.com/ory/dockertest/docker" 29 | "github.com/stretchr/testify/require" 30 | ) 31 | 32 | const chromeImage = "chromedp/headless-shell:70.0.3526.1" 33 | 34 | var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) 35 | 36 | // RunTestChrome compiles all tests in the current working directory for WASM+JS, and runs them in a headless Chrome 37 | // browser using Docker. It will stream test results back to t. 38 | // 39 | // Optionally, the default HTTP handler can be set to serve additional content to the the script. 40 | // 41 | // The caller should specify the "!js" build tag, while all JS tests in the package should include "js" build tag. 42 | func RunTestChrome(t *testing.T, def http.Handler) { 43 | testFile := buildTestJS(t) 44 | defer os.Remove(testFile) 45 | 46 | addr, closer := runChromeInDocker(t) 47 | defer closer() 48 | 49 | rn := newChromeRunner(addr, testFile, def) 50 | testproxy.RunAndReplay(t, rn) 51 | } 52 | 53 | func newChromeRunner(addr, testfile string, def http.Handler) testproxy.Runner { 54 | c := client.New(client.URL("http://" + addr + "/json")) 55 | return &chromeRunner{ 56 | c: c, bin: testfile, def: def, 57 | } 58 | } 59 | 60 | type chromeRunner struct { 61 | c *client.Client 62 | bin string 63 | def http.Handler 64 | } 65 | 66 | func (rn *chromeRunner) RunAndWait(stdout, stderr io.Writer) error { 67 | goroot := goenv.GOROOT() 68 | port := strconv.Itoa(rnd.Intn(10000) + 10000) 69 | 70 | srv := &http.Server{ 71 | Addr: ":" + port, 72 | Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 73 | path := strings.Trim(r.URL.Path, "/") 74 | switch path { 75 | case "", "index.html": 76 | w.Write([]byte(indexHTML)) 77 | case "wasm_exec.js": 78 | http.ServeFile(w, r, filepath.Join(goroot, wasmDir, wasmExecJS)) 79 | case "test.wasm": 80 | http.ServeFile(w, r, rn.bin) 81 | default: 82 | if rn.def != nil { 83 | rn.def.ServeHTTP(w, r) 84 | } else { 85 | w.WriteHeader(http.StatusNotFound) 86 | } 87 | } 88 | }), 89 | } 90 | defer srv.Close() 91 | 92 | errc := make(chan error, 1) 93 | 94 | go func() { 95 | if err := srv.ListenAndServe(); err != nil { 96 | errc <- err 97 | } 98 | }() 99 | 100 | // TODO: discover Docker host IP 101 | surl := "http://172.17.0.1:" + port 102 | 103 | ctx := context.TODO() 104 | c := rn.c 105 | tg, err := c.NewPageTargetWithURL(ctx, "about:blank") 106 | if err != nil { 107 | return err 108 | } 109 | defer c.CloseTarget(ctx, tg) 110 | 111 | logbuf := bytes.NewBuffer(nil) 112 | logf := func(format string, args ...interface{}) { 113 | fmt.Fprintf(logbuf, format, args...) 114 | } 115 | 116 | h, err := chromedp.NewTargetHandler(tg, logf, logf, logf) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | output := make(chan struct{}, 1) 122 | h.OnEvent(func(ev interface{}) { 123 | switch ev := ev.(type) { 124 | case *runtime.EventConsoleAPICalled: 125 | var w io.Writer 126 | switch ev.Type { 127 | case "log": 128 | w = stdout 129 | case "error": 130 | w = stderr 131 | default: 132 | return 133 | } 134 | if len(ev.Args) != 1 { 135 | return 136 | } 137 | var line string 138 | if err := json.Unmarshal(ev.Args[0].Value, &line); err != nil { 139 | select { 140 | case errc <- err: 141 | default: 142 | } 143 | return 144 | } 145 | line += "\n" 146 | w.Write([]byte(line)) 147 | select { 148 | case output <- struct{}{}: 149 | default: 150 | } 151 | case *clog.EventEntryAdded: 152 | stderr.Write([]byte(ev.Entry.Text + "\n")) 153 | select { 154 | case output <- struct{}{}: 155 | default: 156 | } 157 | } 158 | }) 159 | 160 | err = h.Run(ctx) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | err = chromedp.Navigate(surl).Do(ctx, h) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | err = chromedp.WaitEnabled("#runButton").Do(ctx, h) 171 | if err != nil { 172 | return err 173 | } 174 | 175 | // TODO: better wait logic 176 | var res interface{} 177 | err = chromedp.Evaluate("run().then(() => {done = true}).catch(() => {done = true})", &res).Do(ctx, h) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | if err := chromeWaitDone(ctx, h, output, errc); err != nil { 183 | logbuf.WriteTo(stderr) 184 | return err 185 | } 186 | return nil 187 | } 188 | 189 | func chromeWaitDone(ctx context.Context, h *chromedp.TargetHandler, output <-chan struct{}, errc <-chan error) error { 190 | timeout := time.NewTimer(time.Minute) 191 | defer timeout.Stop() 192 | 193 | var done bool 194 | err := chromedp.Evaluate("done", &done).Do(ctx, h) 195 | if err != nil { 196 | return err 197 | } 198 | ticker := time.NewTicker(time.Second / 3) 199 | defer ticker.Stop() 200 | for !done { 201 | err = chromedp.Evaluate("done", &done).Do(ctx, h) 202 | if err != nil { 203 | return err 204 | } 205 | select { 206 | case <-ctx.Done(): 207 | return ctx.Err() 208 | case err = <-errc: 209 | return err 210 | case <-timeout.C: 211 | return context.DeadlineExceeded 212 | case <-output: 213 | timeout.Reset(time.Minute) 214 | case <-ticker.C: 215 | } 216 | } 217 | return nil 218 | } 219 | 220 | func runChromeInDocker(t testing.TB) (string, func()) { 221 | p, err := dockertest.NewPool("") 222 | require.NoError(t, err) 223 | cli := p.Client 224 | 225 | now := time.Now() 226 | if !pullIfNotExists(cli, chromeImage) { 227 | t.SkipNow() 228 | return "", func() {} 229 | } 230 | t.Logf("pulled image %q in %v", chromeImage, time.Since(now)) 231 | 232 | c, err := cli.CreateContainer(docker.CreateContainerOptions{ 233 | Config: &docker.Config{ 234 | Image: chromeImage, 235 | }, 236 | }) 237 | require.NoError(t, err) 238 | 239 | buf := bytes.NewBuffer(nil) 240 | cw, err := cli.AttachToContainerNonBlocking(docker.AttachToContainerOptions{ 241 | Container: c.ID, 242 | OutputStream: buf, 243 | ErrorStream: buf, 244 | Stdout: true, Stderr: true, 245 | Logs: true, Stream: true, 246 | }) 247 | if err != nil { 248 | cli.RemoveContainer(docker.RemoveContainerOptions{ 249 | ID: c.ID, RemoveVolumes: true, Force: true, 250 | }) 251 | require.NoError(t, err) 252 | } 253 | 254 | remove := func() { 255 | cw.Close() 256 | cli.RemoveContainer(docker.RemoveContainerOptions{ 257 | ID: c.ID, RemoveVolumes: true, Force: true, 258 | }) 259 | } 260 | t.Log("running in Chrome in Docker") 261 | 262 | err = cli.StartContainer(c.ID, nil) 263 | if err != nil { 264 | remove() 265 | require.NoError(t, err) 266 | } 267 | 268 | info, err := cli.InspectContainer(c.ID) 269 | if err != nil { 270 | remove() 271 | require.NoError(t, err) 272 | } 273 | addr := info.NetworkSettings.IPAddress + ":9222" 274 | if !waitPort(addr) { 275 | remove() 276 | require.Fail(t, "timeout", "logs:\n%v", buf.String()) 277 | } 278 | return addr, remove 279 | } 280 | 281 | func waitPort(addr string) bool { 282 | for i := 0; i < 10; i++ { 283 | c, err := net.DialTimeout("tcp", addr, time.Second) 284 | if err == nil { 285 | c.Close() 286 | return true 287 | } 288 | time.Sleep(time.Second * 3) 289 | } 290 | return false 291 | } 292 | 293 | const indexHTML = ` 294 | 299 | 300 | 301 | 302 | 303 | Go wasm 304 | 305 | 306 | 307 | 312 | 313 | 338 | 339 | 340 | 341 | 342 | ` 343 | -------------------------------------------------------------------------------- /element.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dennwc/dom/js" 7 | ) 8 | 9 | var _ Node = (*Element)(nil) 10 | 11 | func AsElement(v js.Value) *Element { 12 | if !v.Valid() { 13 | return nil 14 | } 15 | return &Element{NodeBase{v: v}} 16 | } 17 | 18 | func AsNodeList(v js.Value) NodeList { 19 | if !v.Valid() { 20 | return nil 21 | } 22 | arr := make(NodeList, v.Length()) 23 | for i := range arr { 24 | arr[i] = AsElement(v.Index(i)) 25 | } 26 | return arr 27 | } 28 | 29 | var _ Node = (*Element)(nil) 30 | 31 | type Element struct { 32 | NodeBase 33 | } 34 | 35 | type Position string 36 | 37 | const ( 38 | BeforeBegin Position = "beforebegin" 39 | BeforeEnd Position = "beforeend" 40 | AfterBegin Position = "afterbegin" 41 | AfterEnd Position = "afterend" 42 | ) 43 | 44 | // Properties 45 | 46 | // Attributes returns a NamedNodeMap object containing the assigned attributes of the corresponding HTML element. 47 | // func (e *Element) Attributes() NamedNodeMap { 48 | // return e.v.Get("attributes") 49 | // } 50 | 51 | // ClassList returns a DOMTokenList containing the list of class attributes. 52 | func (e *Element) ClassList() *TokenList { 53 | return AsTokenList(e.v.Get("classList")) 54 | } 55 | 56 | // ClassName is a DOMString representing the class of the element. 57 | func (e *Element) ClassName() string { 58 | return e.v.Get("className").String() 59 | } 60 | 61 | // SetClassName is a DOMString representing the class of the element. 62 | func (e *Element) SetClassName(v string) { 63 | e.v.Set("className", v) 64 | } 65 | 66 | // ClientHeight returns a Number representing the inner height of the element. 67 | func (e *Element) ClientHeight() int { 68 | return e.v.Get("clientHeight").Int() 69 | } 70 | 71 | // ClientLeft returns a Number representing the width of the left border of the element. 72 | func (e *Element) ClientLeft() int { 73 | return e.v.Get("clientLeft").Int() 74 | } 75 | 76 | // ClientTop returns a Number representing the width of the top border of the element. 77 | func (e *Element) ClientTop() int { 78 | return e.v.Get("clientTop").Int() 79 | } 80 | 81 | // ClientWidth returns a Number representing the inner width of the element. 82 | func (e *Element) ClientWidth() int { 83 | return e.v.Get("clientWidth").Int() 84 | } 85 | 86 | // ComputedName returns a DOMString containing the label exposed to accessibility. 87 | func (e *Element) ComputedName() string { 88 | return e.v.Get("computedName").String() 89 | } 90 | 91 | // ComputedRole returns a DOMString containing the ARIA role that has been applied to a particular element. 92 | func (e *Element) ComputedRole() string { 93 | return e.v.Get("computedRole").String() 94 | } 95 | 96 | // Id is a DOMString representing the id of the element. 97 | func (e *Element) Id() string { 98 | return e.v.Get("id").String() 99 | } 100 | 101 | // SetId is a DOMString representing the id of the element. 102 | func (e *Element) SetId(v string) { 103 | e.v.Set("id", v) 104 | } 105 | 106 | // InnerHTML is a DOMString representing the markup of the element's content. 107 | func (e *Element) InnerHTML() string { 108 | return e.v.Get("innerHTML").String() 109 | } 110 | 111 | // SetInnerHTML is a DOMString representing the markup of the element's content. 112 | func (e *Element) SetInnerHTML(v string) { 113 | e.v.Set("innerHTML", v) 114 | } 115 | 116 | // LocalName a DOMString representing the local part of the qualified name of the element. 117 | func (e *Element) LocalName() string { 118 | return e.v.Get("localName").String() 119 | } 120 | 121 | // NamespaceURI the namespace URI of the element, or null if it is no namespace. 122 | func (e *Element) NamespaceURI() string { 123 | return e.v.Get("namespaceURI").String() 124 | } 125 | 126 | // OuterHTML is a DOMString representing the markup of the element including its content. When used as a setter, replaces the element with nodes parsed from the given string. 127 | func (e *Element) OuterHTML() string { 128 | return e.v.Get("outerHTML").String() 129 | } 130 | 131 | // SetOuterHTML is a DOMString representing the markup of the element including its content. When used as a setter, replaces the element with nodes parsed from the given string. 132 | func (e *Element) SetOuterHTML(v string) { 133 | e.v.Set("outerHTML", v) 134 | } 135 | 136 | // Prefix a DOMString representing the namespace prefix of the element, or null if no prefix is specified. 137 | func (e *Element) Prefix() string { 138 | return e.v.Get("prefix").String() 139 | } 140 | 141 | // ScrollHeight returns a Number representing the scroll view height of an element. 142 | func (e *Element) ScrollHeight() int { 143 | return e.v.Get("scrollHeight").Int() 144 | } 145 | 146 | // ScrollLeft is a Number representing the left scroll offset of the element. 147 | func (e *Element) ScrollLeft() int { 148 | return e.v.Get("scrollLeft").Int() 149 | } 150 | 151 | // SetScrollLeft is a Number representing the left scroll offset of the element. 152 | func (e *Element) SetScrollLeft(v int) { 153 | e.v.Set("scrollLeft", v) 154 | } 155 | 156 | // ScrollLeftMax returns a Number representing the maximum left scroll offset possible for the element. 157 | func (e *Element) ScrollLeftMax() int { 158 | return e.v.Get("scrollLeftMax").Int() 159 | } 160 | 161 | // ScrollTop a Number representing number of pixels the top of the document is scrolled vertically. 162 | func (e *Element) ScrollTop() int { 163 | return e.v.Get("scrollTop").Int() 164 | } 165 | 166 | // SetScrollTop a Number representing number of pixels the top of the document is scrolled vertically. 167 | func (e *Element) SetScrollTop(v int) { 168 | e.v.Set("scrollTop", v) 169 | } 170 | 171 | // ScrollTopMax returns a Number representing the maximum top scroll offset possible for the element. 172 | func (e *Element) ScrollTopMax() int { 173 | return e.v.Get("scrollTopMax").Int() 174 | } 175 | 176 | // ScrollWidth returns a Number representing the scroll view width of the element. 177 | func (e *Element) ScrollWidth() int { 178 | return e.v.Get("scrollWidth").Int() 179 | } 180 | 181 | // Shadow returns the open shadow root that is hosted by the element, or null if no open shadow root is present. 182 | func (e *Element) ShadowRoot() *ShadowRoot { 183 | return AsShadowRoot(e.v.Get("shadowRoot")) 184 | } 185 | 186 | // Slot returns the name of the shadow DOM slot the element is inserted in. 187 | func (e *Element) Slot() string { 188 | return e.v.Get("slot").String() 189 | } 190 | 191 | // SetSlot returns the name of the shadow DOM slot the element is inserted in. 192 | func (e *Element) SetSlot(v string) { 193 | e.v.Set("slot", v) 194 | } 195 | 196 | // TabStop is a Boolean indicating if the element can receive input focus via the tab key. 197 | func (e *Element) TabStop() bool { 198 | return e.v.Get("tabStop").Bool() 199 | } 200 | 201 | // SetTabStop is a Boolean indicating if the element can receive input focus via the tab key. 202 | func (e *Element) SetTabStop(v bool) { 203 | e.v.Set("tabStop", v) 204 | } 205 | 206 | // TagName returns a String with the name of the tag for the given element. 207 | func (e *Element) TagName() string { 208 | return e.v.Get("tagName").String() 209 | } 210 | 211 | // UndoManager returns the UndoManager associated with the element. 212 | func (e *Element) UndoManager() js.Value { 213 | return e.v.Get("undoManager") 214 | } 215 | 216 | // UndoScope is a Boolean indicating if the element is an undo scope host, or not. 217 | func (e *Element) UndoScope() bool { 218 | return e.v.Get("undoScope").Bool() 219 | } 220 | 221 | // SetUndoScope is a Boolean indicating if the element is an undo scope host, or not. 222 | func (e *Element) SetUndoScope(v bool) { 223 | e.v.Set("undoScope", v) 224 | } 225 | 226 | // Methods 227 | 228 | func (e *Element) SetAttribute(k string, v interface{}) { 229 | e.v.Call("setAttribute", k, fmt.Sprint(v)) 230 | } 231 | 232 | func (e *Element) GetAttribute(k string) js.Value { 233 | return e.v.Call("getAttribute", k) 234 | } 235 | 236 | func (e *Element) RemoveAttribute(k string) { 237 | e.v.Call("removeAttribute", k) 238 | } 239 | 240 | func (e *Element) GetBoundingClientRect() Rect { 241 | rv := e.v.Call("getBoundingClientRect") 242 | x, y := rv.Get("x").Int(), rv.Get("y").Int() 243 | w, h := rv.Get("width").Int(), rv.Get("height").Int() 244 | return Rect{Min: Point{x, y}, Max: Point{x + w, y + h}} 245 | } 246 | 247 | func (e *Element) onMouseEvent(typ string, h MouseEventHandler) { 248 | e.AddEventListener(typ, func(e Event) { 249 | h(e.(*MouseEvent)) 250 | }) 251 | } 252 | 253 | func (e *Element) OnClick(h MouseEventHandler) { 254 | e.onMouseEvent("click", h) 255 | } 256 | 257 | func (e *Element) OnMouseDown(h MouseEventHandler) { 258 | e.onMouseEvent("mousedown", h) 259 | } 260 | 261 | func (e *Element) OnMouseMove(h MouseEventHandler) { 262 | e.onMouseEvent("mousemove", h) 263 | } 264 | 265 | func (e *Element) OnMouseUp(h MouseEventHandler) { 266 | e.onMouseEvent("mouseup", h) 267 | } 268 | 269 | type AttachShadowOpts struct { 270 | Open bool 271 | DeligatesFocus bool 272 | } 273 | 274 | func (e *Element) AttachShadow(opts AttachShadowOpts) *ShadowRoot { 275 | m := map[string]interface{}{} 276 | if opts.Open { 277 | m["mode"] = "open" 278 | } else { 279 | m["mode"] = "closed" 280 | } 281 | m["delegatesFocus"] = opts.DeligatesFocus 282 | return AsShadowRoot(e.v.Call("attachShadow", js.ValueOf(m))) 283 | } 284 | 285 | // InsertAdjacentElement inserts a given element node at a given position relative to the element it is invoked upon. 286 | func (e *Element) InsertAdjacentElement(position Position, newElement *Element) js.Value { 287 | return e.v.Call("insertAdjacentElement", string(position), newElement.v) 288 | } 289 | -------------------------------------------------------------------------------- /htmlElement.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import "github.com/dennwc/dom/js" 4 | 5 | // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement 6 | 7 | func (e *Element) AsHTMLElement() *HTMLElement { 8 | if e == nil { 9 | return nil 10 | } 11 | // TODO: check if the type matches 12 | return &HTMLElement{Element: *e} 13 | } 14 | 15 | type HTMLElement struct { 16 | Element 17 | } 18 | 19 | // Properties 20 | 21 | // AccessKey is a DOMString representing the access key assigned to the element. 22 | func (e *HTMLElement) AccessKey() string { 23 | return e.v.Get("accessKey").String() 24 | } 25 | 26 | // SetAccessKey is a DOMString representing the access key assigned to the element. 27 | func (e *HTMLElement) SetAccessKey(v string) { 28 | e.v.Set("accessKey", v) 29 | } 30 | 31 | // AccessKeyLabel returns a DOMString containing the element's assigned access key. 32 | func (e *HTMLElement) AccessKeyLabel() string { 33 | return e.v.Get("accessKeyLabel").String() 34 | } 35 | 36 | // ContentEditable is a DOMString, where a value of "true" means the element is editable and a value of "false" means it isn't. 37 | func (e *HTMLElement) ContentEditable() string { 38 | return e.v.Get("contentEditable").String() 39 | } 40 | 41 | // SetContentEditable is a DOMString, where a value of "true" means the element is editable and a value of "false" means it isn't. 42 | func (e *HTMLElement) SetContentEditable(v string) { 43 | e.v.Set("contentEditable", v) 44 | } 45 | 46 | // IsContentEditable returns a Boolean that indicates whether or not the content of the element can be edited. 47 | func (e *HTMLElement) IsContentEditable() bool { 48 | return e.v.Get("isContentEditable").Bool() 49 | } 50 | 51 | // Dataset returns a DOMStringMap with which script can read and write the element's custom data attributes (data-*) . 52 | func (e *HTMLElement) Dataset() js.Value { 53 | return e.v.Get("dataset") 54 | } 55 | 56 | // Dir is a DOMString, reflecting the dir global attribute, representing the directionality of the element. Possible values are "ltr", "rtl", and "auto". 57 | func (e *HTMLElement) Dir() string { 58 | return e.v.Get("dir").String() 59 | } 60 | 61 | // SetDir is a DOMString, reflecting the dir global attribute, representing the directionality of the element. Possible values are "ltr", "rtl", and "auto". 62 | func (e *HTMLElement) SetDir(v string) { 63 | e.v.Set("dir", v) 64 | } 65 | 66 | // Draggable is a Boolean indicating if the element can be dragged. 67 | func (e *HTMLElement) Draggable() bool { 68 | return e.v.Get("draggable").Bool() 69 | } 70 | 71 | // SetDraggable is a Boolean indicating if the element can be dragged. 72 | func (e *HTMLElement) SetDraggable(v bool) { 73 | e.v.Set("draggable", v) 74 | } 75 | 76 | // Dropzone returns a DOMSettableTokenList reflecting the dropzone global attribute and describing the behavior of the element regarding a drop operation. 77 | func (e *HTMLElement) Dropzone() *TokenList { 78 | return AsTokenList(e.v.Get("dropzone")) 79 | } 80 | 81 | // Hidden is a Boolean indicating if the element is hidden or not. 82 | func (e *HTMLElement) Hidden() bool { 83 | return e.v.Get("hidden").Bool() 84 | } 85 | 86 | // SetHidden is a Boolean indicating if the element is hidden or not. 87 | func (e *HTMLElement) SetHidden(v bool) { 88 | e.v.Set("hidden", v) 89 | } 90 | 91 | // Inert is a Boolean indicating whether the user agent must act as though the given node is absent for the purposes of user interaction events, in-page text searches ("find in page"), and text selection. 92 | func (e *HTMLElement) Inert() bool { 93 | return e.v.Get("inert").Bool() 94 | } 95 | 96 | // SetInert is a Boolean indicating whether the user agent must act as though the given node is absent for the purposes of user interaction events, in-page text searches ("find in page"), and text selection. 97 | func (e *HTMLElement) SetInert(v bool) { 98 | e.v.Set("inert", v) 99 | } 100 | 101 | // InnerText represents the "rendered" text content of a node and its descendants. As a getter, it approximates the text the user would get if they highlighted the contents of the element with the cursor and then copied it to the clipboard. 102 | func (e *HTMLElement) InnerText() string { 103 | return e.v.Get("innerText").String() 104 | } 105 | 106 | // SetInnerText represents the "rendered" text content of a node and its descendants. As a getter, it approximates the text the user would get if they highlighted the contents of the element with the cursor and then copied it to the clipboard. 107 | func (e *HTMLElement) SetInnerText(v string) { 108 | e.v.Set("innerText", v) 109 | } 110 | 111 | // ItemScope is a Boolean representing the item scope. 112 | func (e *HTMLElement) ItemScope() bool { 113 | return e.v.Get("itemScope").Bool() 114 | } 115 | 116 | // SetItemScope is a Boolean representing the item scope. 117 | func (e *HTMLElement) SetItemScope(v bool) { 118 | e.v.Set("itemScope", v) 119 | } 120 | 121 | // ItemType returns a DOMSettableTokenList… 122 | func (e *HTMLElement) ItemType() *TokenList { 123 | return AsTokenList(e.v.Get("itemType")) 124 | } 125 | 126 | // ItemId is a DOMString representing the item ID. 127 | func (e *HTMLElement) ItemId() string { 128 | return e.v.Get("itemId").String() 129 | } 130 | 131 | // SetItemId is a DOMString representing the item ID. 132 | func (e *HTMLElement) SetItemId(v string) { 133 | e.v.Set("itemId", v) 134 | } 135 | 136 | // ItemRef returns a DOMSettableTokenList… 137 | func (e *HTMLElement) ItemRef() *TokenList { 138 | return AsTokenList(e.v.Get("itemRef")) 139 | } 140 | 141 | // ItemProp returns a DOMSettableTokenList… 142 | func (e *HTMLElement) ItemProp() *TokenList { 143 | return AsTokenList(e.v.Get("itemProp")) 144 | } 145 | 146 | // ItemValue returns a Object representing the item value. 147 | func (e *HTMLElement) ItemValue() js.Value { 148 | return e.v.Get("itemValue") 149 | } 150 | 151 | // SetItemValue returns a Object representing the item value. 152 | func (e *HTMLElement) SetItemValue(v js.Value) { 153 | e.v.Set("itemValue", v) 154 | } 155 | 156 | // Lang is a DOMString representing the language of an element's attributes, text, and element contents. 157 | func (e *HTMLElement) Lang() string { 158 | return e.v.Get("lang").String() 159 | } 160 | 161 | // SetLang is a DOMString representing the language of an element's attributes, text, and element contents. 162 | func (e *HTMLElement) SetLang(v string) { 163 | e.v.Set("lang", v) 164 | } 165 | 166 | // NoModule is a Boolean indicating whether an import script can be executed in user agents that support module scripts. 167 | func (e *HTMLElement) NoModule() bool { 168 | return e.v.Get("noModule").Bool() 169 | } 170 | 171 | // SetNoModule is a Boolean indicating whether an import script can be executed in user agents that support module scripts. 172 | func (e *HTMLElement) SetNoModule(v bool) { 173 | e.v.Set("noModule", v) 174 | } 175 | 176 | // Nonce returns the cryptographic number used once that is used by Content Security Policy to determine whether a given fetch will be allowed to proceed. 177 | func (e *HTMLElement) Nonce() js.Value { 178 | return e.v.Get("nonce") 179 | } 180 | 181 | // SetNonce returns the cryptographic number used once that is used by Content Security Policy to determine whether a given fetch will be allowed to proceed. 182 | func (e *HTMLElement) SetNonce(v js.Value) { 183 | e.v.Set("nonce", v) 184 | } 185 | 186 | // OffsetHeight returns a double containing the height of an element, relative to the layout. 187 | func (e *HTMLElement) OffsetHeight() float64 { 188 | return e.v.Get("offsetHeight").Float() 189 | } 190 | 191 | // OffsetLeft returns a double, the distance from this element's left border to its offsetParent's left border. 192 | func (e *HTMLElement) OffsetLeft() float64 { 193 | return e.v.Get("offsetLeft").Float() 194 | } 195 | 196 | // OffsetParent returns a Element that is the element from which all offset calculations are currently computed. 197 | func (e *HTMLElement) OffsetParent() *Element { 198 | return AsElement(e.v.Get("offsetParent")) 199 | } 200 | 201 | // OffsetTop returns a double, the distance from this element's top border to its offsetParent's top border. 202 | func (e *HTMLElement) OffsetTop() float64 { 203 | return e.v.Get("offsetTop").Float() 204 | } 205 | 206 | // OffsetWidth returns a double containing the width of an element, relative to the layout. 207 | func (e *HTMLElement) OffsetWidth() float64 { 208 | return e.v.Get("offsetWidth").Float() 209 | } 210 | 211 | // Properties returns a HTMLPropertiesCollection… 212 | // func (e *HTMLElement) Properties() HTMLPropertiesCollection { 213 | // return e.v.Get("properties") 214 | // } 215 | 216 | // Spellcheck is a Boolean that controls spell-checking. It is present on all HTML elements, though it doesn't have an effect on all of them. 217 | func (e *HTMLElement) Spellcheck() bool { 218 | return e.v.Get("spellcheck").Bool() 219 | } 220 | 221 | // SetSpellcheck is a Boolean that controls spell-checking. It is present on all HTML elements, though it doesn't have an effect on all of them. 222 | func (e *HTMLElement) SetSpellcheck(v bool) { 223 | e.v.Set("spellcheck", v) 224 | } 225 | 226 | // Style is a CSSStyleDeclaration, an object representing the declarations of an element's style attributes. 227 | func (e *HTMLElement) Style() *Style { 228 | return AsStyle(e.v.Get("style")) 229 | } 230 | 231 | // SetStyle is a CSSStyleDeclaration, an object representing the declarations of an element's style attributes. 232 | func (e *HTMLElement) SetStyle(v *Style) { 233 | e.v.Set("style", v.v) 234 | } 235 | 236 | // TabIndex is a long representing the position of the element in the tabbing order. 237 | func (e *HTMLElement) TabIndex() int { 238 | return e.v.Get("tabIndex").Int() 239 | } 240 | 241 | // SetTabIndex is a long representing the position of the element in the tabbing order. 242 | func (e *HTMLElement) SetTabIndex(v int) { 243 | e.v.Set("tabIndex", v) 244 | } 245 | 246 | // Title is a DOMString containing the text that appears in a popup box when mouse is over the element. 247 | func (e *HTMLElement) Title() string { 248 | return e.v.Get("title").String() 249 | } 250 | 251 | // SetTitle is a DOMString containing the text that appears in a popup box when mouse is over the element. 252 | func (e *HTMLElement) SetTitle(v string) { 253 | e.v.Set("title", v) 254 | } 255 | 256 | // Translate is a Boolean representing the translation. 257 | func (e *HTMLElement) Translate() bool { 258 | return e.v.Get("translate").Bool() 259 | } 260 | 261 | // SetTranslate is a Boolean representing the translation. 262 | func (e *HTMLElement) SetTranslate(v bool) { 263 | e.v.Set("translate", v) 264 | } 265 | 266 | // Methods 267 | 268 | // TODO 269 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/grpc-over-ws/protocol/hello.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: hello.proto 3 | 4 | /* 5 | Package protocol is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | hello.proto 9 | 10 | It has these top-level messages: 11 | HelloReq 12 | HelloResp 13 | */ 14 | package protocol 15 | 16 | import proto "github.com/gogo/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | import _ "github.com/gogo/protobuf/gogoproto" 20 | 21 | import context "golang.org/x/net/context" 22 | import grpc "google.golang.org/grpc" 23 | 24 | import io "io" 25 | 26 | // Reference imports to suppress errors if they are not otherwise used. 27 | var _ = proto.Marshal 28 | var _ = fmt.Errorf 29 | var _ = math.Inf 30 | 31 | // This is a compile-time assertion to ensure that this generated file 32 | // is compatible with the proto package it is being compiled against. 33 | // A compilation error at this line likely means your copy of the 34 | // proto package needs to be updated. 35 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 36 | 37 | type HelloReq struct { 38 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 39 | } 40 | 41 | func (m *HelloReq) Reset() { *m = HelloReq{} } 42 | func (m *HelloReq) String() string { return proto.CompactTextString(m) } 43 | func (*HelloReq) ProtoMessage() {} 44 | func (*HelloReq) Descriptor() ([]byte, []int) { return fileDescriptorHello, []int{0} } 45 | 46 | type HelloResp struct { 47 | Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` 48 | } 49 | 50 | func (m *HelloResp) Reset() { *m = HelloResp{} } 51 | func (m *HelloResp) String() string { return proto.CompactTextString(m) } 52 | func (*HelloResp) ProtoMessage() {} 53 | func (*HelloResp) Descriptor() ([]byte, []int) { return fileDescriptorHello, []int{1} } 54 | 55 | func init() { 56 | proto.RegisterType((*HelloReq)(nil), "HelloReq") 57 | proto.RegisterType((*HelloResp)(nil), "HelloResp") 58 | } 59 | 60 | // Reference imports to suppress errors if they are not otherwise used. 61 | var _ context.Context 62 | var _ grpc.ClientConn 63 | 64 | // This is a compile-time assertion to ensure that this generated file 65 | // is compatible with the grpc package it is being compiled against. 66 | const _ = grpc.SupportPackageIsVersion4 67 | 68 | // Client API for HelloService service 69 | 70 | type HelloServiceClient interface { 71 | Hello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error) 72 | } 73 | 74 | type helloServiceClient struct { 75 | cc *grpc.ClientConn 76 | } 77 | 78 | func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient { 79 | return &helloServiceClient{cc} 80 | } 81 | 82 | func (c *helloServiceClient) Hello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error) { 83 | out := new(HelloResp) 84 | err := grpc.Invoke(ctx, "/HelloService/Hello", in, out, c.cc, opts...) 85 | if err != nil { 86 | return nil, err 87 | } 88 | return out, nil 89 | } 90 | 91 | // Server API for HelloService service 92 | 93 | type HelloServiceServer interface { 94 | Hello(context.Context, *HelloReq) (*HelloResp, error) 95 | } 96 | 97 | func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) { 98 | s.RegisterService(&_HelloService_serviceDesc, srv) 99 | } 100 | 101 | func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 102 | in := new(HelloReq) 103 | if err := dec(in); err != nil { 104 | return nil, err 105 | } 106 | if interceptor == nil { 107 | return srv.(HelloServiceServer).Hello(ctx, in) 108 | } 109 | info := &grpc.UnaryServerInfo{ 110 | Server: srv, 111 | FullMethod: "/HelloService/Hello", 112 | } 113 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 114 | return srv.(HelloServiceServer).Hello(ctx, req.(*HelloReq)) 115 | } 116 | return interceptor(ctx, in, info, handler) 117 | } 118 | 119 | var _HelloService_serviceDesc = grpc.ServiceDesc{ 120 | ServiceName: "HelloService", 121 | HandlerType: (*HelloServiceServer)(nil), 122 | Methods: []grpc.MethodDesc{ 123 | { 124 | MethodName: "Hello", 125 | Handler: _HelloService_Hello_Handler, 126 | }, 127 | }, 128 | Streams: []grpc.StreamDesc{}, 129 | Metadata: "hello.proto", 130 | } 131 | 132 | func (m *HelloReq) Marshal() (dAtA []byte, err error) { 133 | size := m.ProtoSize() 134 | dAtA = make([]byte, size) 135 | n, err := m.MarshalTo(dAtA) 136 | if err != nil { 137 | return nil, err 138 | } 139 | return dAtA[:n], nil 140 | } 141 | 142 | func (m *HelloReq) MarshalTo(dAtA []byte) (int, error) { 143 | var i int 144 | _ = i 145 | var l int 146 | _ = l 147 | if len(m.Name) > 0 { 148 | dAtA[i] = 0xa 149 | i++ 150 | i = encodeVarintHello(dAtA, i, uint64(len(m.Name))) 151 | i += copy(dAtA[i:], m.Name) 152 | } 153 | return i, nil 154 | } 155 | 156 | func (m *HelloResp) Marshal() (dAtA []byte, err error) { 157 | size := m.ProtoSize() 158 | dAtA = make([]byte, size) 159 | n, err := m.MarshalTo(dAtA) 160 | if err != nil { 161 | return nil, err 162 | } 163 | return dAtA[:n], nil 164 | } 165 | 166 | func (m *HelloResp) MarshalTo(dAtA []byte) (int, error) { 167 | var i int 168 | _ = i 169 | var l int 170 | _ = l 171 | if len(m.Text) > 0 { 172 | dAtA[i] = 0xa 173 | i++ 174 | i = encodeVarintHello(dAtA, i, uint64(len(m.Text))) 175 | i += copy(dAtA[i:], m.Text) 176 | } 177 | return i, nil 178 | } 179 | 180 | func encodeVarintHello(dAtA []byte, offset int, v uint64) int { 181 | for v >= 1<<7 { 182 | dAtA[offset] = uint8(v&0x7f | 0x80) 183 | v >>= 7 184 | offset++ 185 | } 186 | dAtA[offset] = uint8(v) 187 | return offset + 1 188 | } 189 | func (m *HelloReq) ProtoSize() (n int) { 190 | var l int 191 | _ = l 192 | l = len(m.Name) 193 | if l > 0 { 194 | n += 1 + l + sovHello(uint64(l)) 195 | } 196 | return n 197 | } 198 | 199 | func (m *HelloResp) ProtoSize() (n int) { 200 | var l int 201 | _ = l 202 | l = len(m.Text) 203 | if l > 0 { 204 | n += 1 + l + sovHello(uint64(l)) 205 | } 206 | return n 207 | } 208 | 209 | func sovHello(x uint64) (n int) { 210 | for { 211 | n++ 212 | x >>= 7 213 | if x == 0 { 214 | break 215 | } 216 | } 217 | return n 218 | } 219 | func sozHello(x uint64) (n int) { 220 | return sovHello(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 221 | } 222 | func (m *HelloReq) Unmarshal(dAtA []byte) error { 223 | l := len(dAtA) 224 | iNdEx := 0 225 | for iNdEx < l { 226 | preIndex := iNdEx 227 | var wire uint64 228 | for shift := uint(0); ; shift += 7 { 229 | if shift >= 64 { 230 | return ErrIntOverflowHello 231 | } 232 | if iNdEx >= l { 233 | return io.ErrUnexpectedEOF 234 | } 235 | b := dAtA[iNdEx] 236 | iNdEx++ 237 | wire |= (uint64(b) & 0x7F) << shift 238 | if b < 0x80 { 239 | break 240 | } 241 | } 242 | fieldNum := int32(wire >> 3) 243 | wireType := int(wire & 0x7) 244 | if wireType == 4 { 245 | return fmt.Errorf("proto: HelloReq: wiretype end group for non-group") 246 | } 247 | if fieldNum <= 0 { 248 | return fmt.Errorf("proto: HelloReq: illegal tag %d (wire type %d)", fieldNum, wire) 249 | } 250 | switch fieldNum { 251 | case 1: 252 | if wireType != 2 { 253 | return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) 254 | } 255 | var stringLen uint64 256 | for shift := uint(0); ; shift += 7 { 257 | if shift >= 64 { 258 | return ErrIntOverflowHello 259 | } 260 | if iNdEx >= l { 261 | return io.ErrUnexpectedEOF 262 | } 263 | b := dAtA[iNdEx] 264 | iNdEx++ 265 | stringLen |= (uint64(b) & 0x7F) << shift 266 | if b < 0x80 { 267 | break 268 | } 269 | } 270 | intStringLen := int(stringLen) 271 | if intStringLen < 0 { 272 | return ErrInvalidLengthHello 273 | } 274 | postIndex := iNdEx + intStringLen 275 | if postIndex > l { 276 | return io.ErrUnexpectedEOF 277 | } 278 | m.Name = string(dAtA[iNdEx:postIndex]) 279 | iNdEx = postIndex 280 | default: 281 | iNdEx = preIndex 282 | skippy, err := skipHello(dAtA[iNdEx:]) 283 | if err != nil { 284 | return err 285 | } 286 | if skippy < 0 { 287 | return ErrInvalidLengthHello 288 | } 289 | if (iNdEx + skippy) > l { 290 | return io.ErrUnexpectedEOF 291 | } 292 | iNdEx += skippy 293 | } 294 | } 295 | 296 | if iNdEx > l { 297 | return io.ErrUnexpectedEOF 298 | } 299 | return nil 300 | } 301 | func (m *HelloResp) Unmarshal(dAtA []byte) error { 302 | l := len(dAtA) 303 | iNdEx := 0 304 | for iNdEx < l { 305 | preIndex := iNdEx 306 | var wire uint64 307 | for shift := uint(0); ; shift += 7 { 308 | if shift >= 64 { 309 | return ErrIntOverflowHello 310 | } 311 | if iNdEx >= l { 312 | return io.ErrUnexpectedEOF 313 | } 314 | b := dAtA[iNdEx] 315 | iNdEx++ 316 | wire |= (uint64(b) & 0x7F) << shift 317 | if b < 0x80 { 318 | break 319 | } 320 | } 321 | fieldNum := int32(wire >> 3) 322 | wireType := int(wire & 0x7) 323 | if wireType == 4 { 324 | return fmt.Errorf("proto: HelloResp: wiretype end group for non-group") 325 | } 326 | if fieldNum <= 0 { 327 | return fmt.Errorf("proto: HelloResp: illegal tag %d (wire type %d)", fieldNum, wire) 328 | } 329 | switch fieldNum { 330 | case 1: 331 | if wireType != 2 { 332 | return fmt.Errorf("proto: wrong wireType = %d for field Text", wireType) 333 | } 334 | var stringLen uint64 335 | for shift := uint(0); ; shift += 7 { 336 | if shift >= 64 { 337 | return ErrIntOverflowHello 338 | } 339 | if iNdEx >= l { 340 | return io.ErrUnexpectedEOF 341 | } 342 | b := dAtA[iNdEx] 343 | iNdEx++ 344 | stringLen |= (uint64(b) & 0x7F) << shift 345 | if b < 0x80 { 346 | break 347 | } 348 | } 349 | intStringLen := int(stringLen) 350 | if intStringLen < 0 { 351 | return ErrInvalidLengthHello 352 | } 353 | postIndex := iNdEx + intStringLen 354 | if postIndex > l { 355 | return io.ErrUnexpectedEOF 356 | } 357 | m.Text = string(dAtA[iNdEx:postIndex]) 358 | iNdEx = postIndex 359 | default: 360 | iNdEx = preIndex 361 | skippy, err := skipHello(dAtA[iNdEx:]) 362 | if err != nil { 363 | return err 364 | } 365 | if skippy < 0 { 366 | return ErrInvalidLengthHello 367 | } 368 | if (iNdEx + skippy) > l { 369 | return io.ErrUnexpectedEOF 370 | } 371 | iNdEx += skippy 372 | } 373 | } 374 | 375 | if iNdEx > l { 376 | return io.ErrUnexpectedEOF 377 | } 378 | return nil 379 | } 380 | func skipHello(dAtA []byte) (n int, err error) { 381 | l := len(dAtA) 382 | iNdEx := 0 383 | for iNdEx < l { 384 | var wire uint64 385 | for shift := uint(0); ; shift += 7 { 386 | if shift >= 64 { 387 | return 0, ErrIntOverflowHello 388 | } 389 | if iNdEx >= l { 390 | return 0, io.ErrUnexpectedEOF 391 | } 392 | b := dAtA[iNdEx] 393 | iNdEx++ 394 | wire |= (uint64(b) & 0x7F) << shift 395 | if b < 0x80 { 396 | break 397 | } 398 | } 399 | wireType := int(wire & 0x7) 400 | switch wireType { 401 | case 0: 402 | for shift := uint(0); ; shift += 7 { 403 | if shift >= 64 { 404 | return 0, ErrIntOverflowHello 405 | } 406 | if iNdEx >= l { 407 | return 0, io.ErrUnexpectedEOF 408 | } 409 | iNdEx++ 410 | if dAtA[iNdEx-1] < 0x80 { 411 | break 412 | } 413 | } 414 | return iNdEx, nil 415 | case 1: 416 | iNdEx += 8 417 | return iNdEx, nil 418 | case 2: 419 | var length int 420 | for shift := uint(0); ; shift += 7 { 421 | if shift >= 64 { 422 | return 0, ErrIntOverflowHello 423 | } 424 | if iNdEx >= l { 425 | return 0, io.ErrUnexpectedEOF 426 | } 427 | b := dAtA[iNdEx] 428 | iNdEx++ 429 | length |= (int(b) & 0x7F) << shift 430 | if b < 0x80 { 431 | break 432 | } 433 | } 434 | iNdEx += length 435 | if length < 0 { 436 | return 0, ErrInvalidLengthHello 437 | } 438 | return iNdEx, nil 439 | case 3: 440 | for { 441 | var innerWire uint64 442 | var start int = iNdEx 443 | for shift := uint(0); ; shift += 7 { 444 | if shift >= 64 { 445 | return 0, ErrIntOverflowHello 446 | } 447 | if iNdEx >= l { 448 | return 0, io.ErrUnexpectedEOF 449 | } 450 | b := dAtA[iNdEx] 451 | iNdEx++ 452 | innerWire |= (uint64(b) & 0x7F) << shift 453 | if b < 0x80 { 454 | break 455 | } 456 | } 457 | innerWireType := int(innerWire & 0x7) 458 | if innerWireType == 4 { 459 | break 460 | } 461 | next, err := skipHello(dAtA[start:]) 462 | if err != nil { 463 | return 0, err 464 | } 465 | iNdEx = start + next 466 | } 467 | return iNdEx, nil 468 | case 4: 469 | return iNdEx, nil 470 | case 5: 471 | iNdEx += 4 472 | return iNdEx, nil 473 | default: 474 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 475 | } 476 | } 477 | panic("unreachable") 478 | } 479 | 480 | var ( 481 | ErrInvalidLengthHello = fmt.Errorf("proto: negative length found during unmarshaling") 482 | ErrIntOverflowHello = fmt.Errorf("proto: integer overflow") 483 | ) 484 | 485 | func init() { proto.RegisterFile("hello.proto", fileDescriptorHello) } 486 | 487 | var fileDescriptorHello = []byte{ 488 | // 176 bytes of a gzipped FileDescriptorProto 489 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9, 490 | 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x97, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 491 | 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0x4f, 0xcf, 0xd7, 0x07, 0x0b, 0x27, 0x95, 0xa6, 0x81, 0x79, 492 | 0x60, 0x0e, 0x98, 0x05, 0x51, 0xae, 0x24, 0xc7, 0xc5, 0xe1, 0x01, 0xd2, 0x1d, 0x94, 0x5a, 0x28, 493 | 0x24, 0xc4, 0xc5, 0x92, 0x97, 0x98, 0x9b, 0x2a, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x19, 0x04, 0x66, 494 | 0x2b, 0xc9, 0x73, 0x71, 0x42, 0xe5, 0x8b, 0x0b, 0x40, 0x0a, 0x4a, 0x52, 0x2b, 0x4a, 0x60, 0x0a, 495 | 0x40, 0x6c, 0x23, 0x3d, 0x2e, 0x1e, 0xb0, 0x82, 0xe0, 0xd4, 0xa2, 0xb2, 0xcc, 0xe4, 0x54, 0x21, 496 | 0x39, 0x2e, 0x56, 0x30, 0x5f, 0x88, 0x53, 0x0f, 0x66, 0xb0, 0x14, 0x97, 0x1e, 0xdc, 0x0c, 0x27, 497 | 0xb9, 0x13, 0x0f, 0xe5, 0x18, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 498 | 0x39, 0x86, 0x05, 0x8f, 0xe5, 0x18, 0xa3, 0x38, 0xc0, 0xae, 0x49, 0xce, 0xcf, 0x49, 0x62, 0x03, 499 | 0xb3, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xd0, 0xc3, 0x9b, 0x1f, 0xd5, 0x00, 0x00, 0x00, 500 | } 501 | --------------------------------------------------------------------------------