From aa1384fbef3382cfd050fd0d28a0a0d32f172015 Mon Sep 17 00:00:00 2001 From: ferealqq Date: Sun, 29 May 2022 20:36:43 +0300 Subject: [PATCH 1/3] Add support for adding external scripts to document. This PR adds support for adding external scripts to the document. Not sure if the way that I implemented 'attributes' to the AddScript function is the most convinent. Let me know what solution could fit better. Created tests for AddScript function. --- dom.go | 10 ++++++++++ dom_test.go | 9 +++++++++ testdata/TestAddScript.want.txt | 7 +++++++ 3 files changed, 26 insertions(+) create mode 100644 testdata/TestAddScript.want.txt diff --git a/dom.go b/dom.go index 5c75ed63..5fe17e85 100644 --- a/dom.go +++ b/dom.go @@ -1282,6 +1282,16 @@ func AddStylesheet(url string) { global().Get("document").Get("head").Call("appendChild", link) } +// AddScript adds an external script to the document +func AddScript(url string, attributes map[string]interface{}) { + script := global().Get("document").Call("createElement", "script") + script.Set("src", url) + for k, v := range attributes { + script.Set(k, v) + } + global().Get("document").Get("head").Call("appendChild", script) +} + type jsFunc interface { Release() } diff --git a/dom_test.go b/dom_test.go index dd0be82d..5e8f2e12 100644 --- a/dom_test.go +++ b/dom_test.go @@ -1124,6 +1124,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", 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..f5459b99 --- /dev/null +++ b/testdata/TestAddScript.want.txt @@ -0,0 +1,7 @@ +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") +global.Get("document").Get("head") +global.Get("document").Get("head").Call("appendChild", jsObject(global.Get("document").Call("createElement", "script"))) \ No newline at end of file From bd572ab6cda4e9074aea84c724f23c41a040d66e Mon Sep 17 00:00:00 2001 From: ferealqq Date: Mon, 30 May 2022 20:04:12 +0300 Subject: [PATCH 2/3] AddScript guarntees that the script was loaded. AddScript guarntees that the script was loaded or returns a error. --- dom.go | 49 ++++++++++++++++++++++++++++++++- dom_test.go | 3 +- testdata/TestAddScript.want.txt | 1 + 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/dom.go b/dom.go index 5fe17e85..f7f17d42 100644 --- a/dom.go +++ b/dom.go @@ -1,7 +1,10 @@ package vecty import ( + "errors" "reflect" + "sync" + "time" ) // batch renderer singleton @@ -1282,14 +1285,58 @@ 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, attributes map[string]interface{}) { +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.Delete("onload") + return nil + }) + script.Set("onload", f) global().Get("document").Get("head").Call("appendChild", script) + + var err error + var wg sync.WaitGroup + wg.Add(1) + go func(t time.Duration) { + + 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 { + err = ErrTimeout + wg.Done() + break loop + } + } + } + }(timeout) + wg.Wait() + + return err } type jsFunc interface { diff --git a/dom_test.go b/dom_test.go index 5e8f2e12..e58c373c 100644 --- a/dom_test.go +++ b/dom_test.go @@ -3,6 +3,7 @@ package vecty import ( "fmt" "testing" + "time" ) type testCore struct{ Core } @@ -1130,7 +1131,7 @@ func TestAddScript(t *testing.T) { ts := testSuite(t) defer ts.done() - AddScript("https://google.com/foobar.js", map[string]interface{}{"crossorigin": "anonymous"}) + AddScript("https://google.com/foobar.js", time.Millisecond * 100,map[string]interface{}{"crossorigin": "anonymous"}) } func TestKeyedChild_DifferentType(t *testing.T) { diff --git a/testdata/TestAddScript.want.txt b/testdata/TestAddScript.want.txt index f5459b99..9cbe3f44 100644 --- a/testdata/TestAddScript.want.txt +++ b/testdata/TestAddScript.want.txt @@ -2,6 +2,7 @@ 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").Set("onload", func) global.Get("document") global.Get("document").Get("head") global.Get("document").Get("head").Call("appendChild", jsObject(global.Get("document").Call("createElement", "script"))) \ No newline at end of file From f839ebd557fb64d376a2747ceaa59914fe4edf00 Mon Sep 17 00:00:00 2001 From: ferealqq Date: Mon, 30 May 2022 20:59:08 +0300 Subject: [PATCH 3/3] AddScript removeEventListener when returning AddScript removes "onload" event listener when the function has executed. --- dom.go | 12 +++++++----- testdata/TestAddScript.want.txt | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/dom.go b/dom.go index f7f17d42..20468b6b 100644 --- a/dom.go +++ b/dom.go @@ -1305,16 +1305,16 @@ func AddScript(url string, timeout time.Duration, attributes map[string]interfac f = funcOf(func(this jsObject, args []jsObject) interface{} { defer f.Release() close(quitCh) - script.Delete("onload") + script.Call("removeEventListener", "load", f) return nil }) - script.Set("onload", f) + 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) { + go func(t time.Duration, script jsObject, f jsFunc) { tic := time.NewTicker(10 * time.Millisecond) start := time.Now() @@ -1327,13 +1327,15 @@ func AddScript(url string, timeout time.Duration, attributes map[string]interfac break loop case <-tic.C: if time.Since(start) > t { + defer f.Release() + defer wg.Done() err = ErrTimeout - wg.Done() + script.Call("removeEventListener", "load", f) break loop } } } - }(timeout) + }(timeout,script, f) wg.Wait() return err diff --git a/testdata/TestAddScript.want.txt b/testdata/TestAddScript.want.txt index 9cbe3f44..4e545741 100644 --- a/testdata/TestAddScript.want.txt +++ b/testdata/TestAddScript.want.txt @@ -2,7 +2,8 @@ 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").Set("onload", func) +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"))) \ No newline at end of file +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