-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4f9b361
commit c84fdb4
Showing
10 changed files
with
636 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Env file defines useful environment variables for editors. | ||
GOOS=js | ||
GOARCH=wasm |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,7 @@ | ||
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= | ||
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= | ||
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8= | ||
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= | ||
github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8= | ||
github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= | ||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= | ||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= | ||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= | ||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
//go:build js && wasm | ||
|
||
package worker | ||
|
||
import ( | ||
"github.com/hack-pad/safejs" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// MessageEvent is received from the channel returned by Listen(). | ||
// Represents a JS MessageEvent. | ||
type MessageEvent struct { | ||
data safejs.Value | ||
err error | ||
target *messagePort | ||
} | ||
|
||
// Data returns this event's data or a parse error | ||
func (e MessageEvent) Data() (safejs.Value, error) { | ||
return e.data, errors.Wrapf(e.err, "failed to parse MessageEvent %+v", e.data) | ||
} | ||
|
||
func parseMessageEvent(v safejs.Value) MessageEvent { | ||
value, err := v.Get("target") | ||
if err != nil { | ||
return MessageEvent{err: err} | ||
} | ||
target, err := wrapMessagePort(value) | ||
if err != nil { | ||
return MessageEvent{err: err} | ||
} | ||
data, err := v.Get("data") | ||
if err != nil { | ||
return MessageEvent{err: err} | ||
} | ||
return MessageEvent{ | ||
data: data, | ||
target: target, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
//go:build js && wasm | ||
|
||
package worker | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hack-pad/safejs" | ||
) | ||
|
||
type messagePort struct { | ||
jsMessagePort safejs.Value | ||
} | ||
|
||
func wrapMessagePort(v safejs.Value) (*messagePort, error) { | ||
someMethod, err := v.Get("postMessage") | ||
if err != nil { | ||
return nil, err | ||
} | ||
if truthy, err := someMethod.Truthy(); err != nil || !truthy { | ||
return nil, fmt.Errorf("invalid MessagePort value: postMessage is not a function") | ||
} | ||
return &messagePort{v}, nil | ||
} | ||
|
||
func (p *messagePort) PostMessage(data safejs.Value, transfers []safejs.Value) error { | ||
args := append([]any{data}, toJSSlice(transfers)) | ||
_, err := p.jsMessagePort.Call("postMessage", args...) | ||
return err | ||
} | ||
|
||
func toJSSlice[Type any](slice []Type) []any { | ||
newSlice := make([]any, len(slice)) | ||
for i := range slice { | ||
newSlice[i] = slice[i] | ||
} | ||
return newSlice | ||
} | ||
|
||
func (p *messagePort) Listen(ctx context.Context) (_ <-chan MessageEvent, err error) { | ||
ctx, cancel := context.WithCancel(ctx) | ||
defer func() { | ||
if err != nil { | ||
cancel() | ||
} | ||
}() | ||
|
||
events := make(chan MessageEvent) | ||
messageHandler, err := nonBlocking(func(args []safejs.Value) { | ||
events <- parseMessageEvent(args[0]) | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
errorHandler, err := nonBlocking(func(args []safejs.Value) { | ||
events <- parseMessageEvent(args[0]) | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
go func() { | ||
<-ctx.Done() | ||
_, err := p.jsMessagePort.Call("removeEventListener", "message", messageHandler) | ||
if err == nil { | ||
messageHandler.Release() | ||
} | ||
_, err = p.jsMessagePort.Call("removeEventListener", "messageerror", errorHandler) | ||
if err == nil { | ||
errorHandler.Release() | ||
} | ||
close(events) | ||
}() | ||
_, err = p.jsMessagePort.Call("addEventListener", "message", messageHandler) | ||
if err != nil { | ||
return nil, err | ||
} | ||
_, err = p.jsMessagePort.Call("addEventListener", "messageerror", errorHandler) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if start, err := p.jsMessagePort.Get("start"); err == nil { | ||
if truthy, err := start.Truthy(); err == nil && truthy { | ||
if _, err := p.jsMessagePort.Call("start"); err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
return events, nil | ||
} | ||
|
||
func nonBlocking(fn func(args []safejs.Value)) (safejs.Func, error) { | ||
return safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) any { | ||
go fn(args) | ||
return nil | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
//go:build js && wasm | ||
|
||
package worker | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hack-pad/safejs" | ||
) | ||
|
||
// GlobalSelf represents the global scope, named "self", in the context of using Workers. | ||
// Supports sending and receiving messages via PostMessage() and Listen(). | ||
type GlobalSelf struct { | ||
self safejs.Value | ||
port *messagePort | ||
} | ||
|
||
// Self returns the global "self" | ||
func Self() (*GlobalSelf, error) { | ||
self, err := safejs.Global().Get("self") | ||
if err != nil { | ||
return nil, err | ||
} | ||
port, err := wrapMessagePort(self) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &GlobalSelf{ | ||
self: self, | ||
port: port, | ||
}, nil | ||
} | ||
|
||
// PostMessage sends data in a message to the main thread that spawned it, | ||
// optionally transferring ownership of all items in transfers. | ||
// | ||
// The data may be any value handled by the "structured clone algorithm", which includes cyclical references. | ||
// | ||
// Transfers is an optional array of Transferable objects to transfer ownership of. | ||
// If the ownership of an object is transferred, it becomes unusable in the context it was sent from and becomes available only to the worker it was sent to. | ||
// Transferable objects are instances of classes like ArrayBuffer, MessagePort or ImageBitmap objects that can be transferred. | ||
// null is not an acceptable value for transfer. | ||
func (s *GlobalSelf) PostMessage(message safejs.Value, transfers []safejs.Value) error { | ||
return s.port.PostMessage(message, transfers) | ||
} | ||
|
||
// Listen sends message events on a channel for events fired by worker.postMessage() calls inside the main thread's global scope. | ||
// Stops the listener and closes the channel when ctx is canceled. | ||
func (s *GlobalSelf) Listen(ctx context.Context) (<-chan MessageEvent, error) { | ||
return s.port.Listen(ctx) | ||
} | ||
|
||
// Close discards any tasks queued in the global scope's event loop, effectively closing this particular scope. | ||
func (s *GlobalSelf) Close() error { | ||
_, err := s.self.Call("close") | ||
return err | ||
} | ||
|
||
// Name returns the name that the Worker was (optionally) given when it was created. | ||
func (s *GlobalSelf) Name() (string, error) { | ||
name, err := s.self.Get("name") | ||
if err != nil { | ||
return "", err | ||
} | ||
return name.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
//go:build js && wasm | ||
|
||
package worker | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hack-pad/safejs" | ||
) | ||
|
||
func TestSelf(t *testing.T) { | ||
t.Parallel() | ||
self, err := Self() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !self.self.Equal(safejs.MustGetGlobal("self")) { | ||
t.Error("self is not equal to the global self") | ||
} | ||
} | ||
|
||
func TestSelfName(t *testing.T) { | ||
t.Parallel() | ||
self, err := Self() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
name, err := self.Name() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if name != "" { | ||
t.Errorf("Expected %q, got %q", "", name) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,101 @@ | ||
//go:build js && wasm | ||
// +build js,wasm | ||
|
||
// Package worker provides a Web Workers driver for Go code compiled to WebAssembly. | ||
package worker | ||
|
||
import "errors" | ||
import ( | ||
"context" | ||
|
||
// Worker is a Web Worker, which represents a background task that can be created via script. | ||
// Workers can send messages back to its creator. | ||
type Worker struct{} | ||
"github.com/hack-pad/safejs" | ||
) | ||
|
||
// NewWorker returns a new Worker | ||
func NewWorker() (*Worker, error) { | ||
return nil, errors.New("not implemented") | ||
var ( | ||
jsWorker = safejs.MustGetGlobal("Worker") | ||
jsURL = safejs.MustGetGlobal("URL") | ||
jsBlob = safejs.MustGetGlobal("Blob") | ||
) | ||
|
||
// Worker is a Web Worker, which represents a background task created via a script. | ||
// Use Listen() and PostMessage() to communicate with the worker. | ||
type Worker struct { | ||
worker safejs.Value | ||
port *messagePort | ||
} | ||
|
||
// Options contains optional configuration for new Workers | ||
type Options struct { | ||
// Name specifies an identifying name for the DedicatedWorkerGlobalScope representing the scope of the worker, which is mainly useful for debugging purposes. | ||
Name string | ||
} | ||
|
||
func (w Options) toJSValue() (safejs.Value, error) { | ||
options := make(map[string]any) | ||
if w.Name != "" { | ||
options["name"] = w.Name | ||
} | ||
return safejs.ValueOf(options) | ||
} | ||
|
||
// New starts a worker with the given script's URL and returns it | ||
func New(url string, options Options) (*Worker, error) { | ||
jsOptions, err := options.toJSValue() | ||
if err != nil { | ||
return nil, err | ||
} | ||
worker, err := jsWorker.New(url, jsOptions) | ||
if err != nil { | ||
return nil, err | ||
} | ||
port, err := wrapMessagePort(worker) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Worker{ | ||
port: port, | ||
worker: worker, | ||
}, nil | ||
} | ||
|
||
// NewFromScript is like New, but starts the worker with the given script (in JavaScript) | ||
func NewFromScript(jsScript string, options Options) (*Worker, error) { | ||
blob, err := jsBlob.New([]any{jsScript}, map[string]any{ | ||
"type": "text/javascript", | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
objectURL, err := jsURL.Call("createObjectURL", blob) | ||
if err != nil { | ||
return nil, err | ||
} | ||
objectURLStr, err := objectURL.String() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return New(objectURLStr, options) | ||
} | ||
|
||
// Terminate immediately terminates the Worker. | ||
// This does not offer the worker an opportunity to finish its operations; it is stopped at once. | ||
func (w *Worker) Terminate() error { | ||
_, err := w.worker.Call("terminate") | ||
return err | ||
} | ||
|
||
// PostMessage sends data in a message to the worker, optionally transferring ownership of all items in transfers. | ||
// | ||
// The data may be any value handled by the "structured clone algorithm", which includes cyclical references. | ||
// | ||
// Transfers is an optional array of Transferable objects to transfer ownership of. | ||
// If the ownership of an object is transferred, it becomes unusable in the context it was sent from and becomes available only to the worker it was sent to. | ||
// Transferable objects are instances of classes like ArrayBuffer, MessagePort or ImageBitmap objects that can be transferred. | ||
// null is not an acceptable value for transfer. | ||
func (w *Worker) PostMessage(data safejs.Value, transfers []safejs.Value) error { | ||
return w.port.PostMessage(data, transfers) | ||
} | ||
|
||
// Listen sends message events on a channel for events fired by self.postMessage() calls inside the Worker's global scope. | ||
// Stops the listener and closes the channel when ctx is canceled. | ||
func (w *Worker) Listen(ctx context.Context) (<-chan MessageEvent, error) { | ||
return w.port.Listen(ctx) | ||
} |
Oops, something went wrong.