diff --git a/dom.go b/dom.go index 5c75ed63..20468b6b 100644 --- a/dom.go +++ b/dom.go @@ -1,7 +1,10 @@ package vecty import ( + "errors" "reflect" + "sync" + "time" ) // batch renderer singleton @@ -1282,6 +1285,62 @@ func AddStylesheet(url string) { global().Get("document").Get("head").Call("appendChild", link) } +var ErrTimeout = errors.New("timeout while obtaining script from URL") +var ErrTimeoutGreaterThan = errors.New("timeout value has to be greater than zero") + +// AddScript adds an external script to the document +func AddScript(url string, timeout time.Duration, attributes map[string]interface{}) error { + if timeout <= 0 { + return ErrTimeoutGreaterThan + } + script := global().Get("document").Call("createElement", "script") + script.Set("src", url) + for k, v := range attributes { + script.Set(k, v) + } + + quitCh := make(chan struct{}) + + var f jsFunc + f = funcOf(func(this jsObject, args []jsObject) interface{} { + defer f.Release() + close(quitCh) + script.Call("removeEventListener", "load", f) + return nil + }) + script.Call("addEventListener", "load", f) + global().Get("document").Get("head").Call("appendChild", script) + + var err error + var wg sync.WaitGroup + wg.Add(1) + go func(t time.Duration, script jsObject, f jsFunc) { + + tic := time.NewTicker(10 * time.Millisecond) + start := time.Now() + loop: + for { + select { + // Stop the execution of this goroutine when the script has loaded + case <-quitCh: + wg.Done() + break loop + case <-tic.C: + if time.Since(start) > t { + defer f.Release() + defer wg.Done() + err = ErrTimeout + script.Call("removeEventListener", "load", f) + break loop + } + } + } + }(timeout,script, f) + wg.Wait() + + return err +} + type jsFunc interface { Release() } diff --git a/dom_test.go b/dom_test.go index dd0be82d..e58c373c 100644 --- a/dom_test.go +++ b/dom_test.go @@ -3,6 +3,7 @@ package vecty import ( "fmt" "testing" + "time" ) type testCore struct{ Core } @@ -1124,6 +1125,15 @@ func TestAddStylesheet(t *testing.T) { AddStylesheet("https://google.com/foobar.css") } +// TestAddScript tests that the AddScript performs the correct DOM +// operations. +func TestAddScript(t *testing.T) { + ts := testSuite(t) + defer ts.done() + + AddScript("https://google.com/foobar.js", time.Millisecond * 100,map[string]interface{}{"crossorigin": "anonymous"}) +} + func TestKeyedChild_DifferentType(t *testing.T) { ts := testSuite(t) defer ts.done() diff --git a/testdata/TestAddScript.want.txt b/testdata/TestAddScript.want.txt new file mode 100644 index 00000000..4e545741 --- /dev/null +++ b/testdata/TestAddScript.want.txt @@ -0,0 +1,9 @@ +global.Get("document") +global.Get("document").Call("createElement", "script") +global.Get("document").Call("createElement", "script").Set("src", "https://google.com/foobar.js") +global.Get("document").Call("createElement", "script").Set("crossorigin", "anonymous") +global.Get("document").Call("createElement", "script").Call("addEventListener", "load", func) +global.Get("document") +global.Get("document").Get("head") +global.Get("document").Get("head").Call("appendChild", jsObject(global.Get("document").Call("createElement", "script"))) +global.Get("document").Call("createElement", "script").Call("removeEventListener", "load", func) \ No newline at end of file