├── .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 |
--------------------------------------------------------------------------------