From 95475b9f2e58ac6a32d2ebd29023ae57877aa725 Mon Sep 17 00:00:00 2001 From: Joey Bloom <15joeybloom@gmail.com> Date: Mon, 25 Nov 2024 04:22:10 -0600 Subject: [PATCH 1/3] fix bug in WaitLoad when event has circular reference --- fixtures/wait-load-circular-reference.html | 14 ++++++++ lib/js/helper.go | 2 +- page_test.go | 38 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 fixtures/wait-load-circular-reference.html diff --git a/fixtures/wait-load-circular-reference.html b/fixtures/wait-load-circular-reference.html new file mode 100644 index 00000000..f05d283f --- /dev/null +++ b/fixtures/wait-load-circular-reference.html @@ -0,0 +1,14 @@ + +
+ + + + + + diff --git a/lib/js/helper.go b/lib/js/helper.go index c8e52823..bc1611f3 100644 --- a/lib/js/helper.go +++ b/lib/js/helper.go @@ -117,7 +117,7 @@ var WaitIdle = &Function{ // WaitLoad ... var WaitLoad = &Function{ Name: "waitLoad", - Definition: `function(){const n=this===window;return new Promise((e,t)=>{if(n){if("complete"===document.readyState)return e();window.addEventListener("load",e)}else void 0===this.complete||this.complete?e():(this.addEventListener("load",e),this.addEventListener("error",t))})}`, + Definition: `function(){const n=this===window;return new Promise((e,t)=>{if(n){if("complete"===document.readyState)return e();window.addEventListener("load",_=>e())}else void 0===this.complete||this.complete?e():(this.addEventListener("load",_=>e()),this.addEventListener("error",t))})}`, Dependencies: []*Function{}, } diff --git a/page_test.go b/page_test.go index 0bb3fdda..92ab20c1 100644 --- a/page_test.go +++ b/page_test.go @@ -946,6 +946,44 @@ func TestPageWaitLoadErr(t *testing.T) { }) } +func TestPageWaitLoadCircularReference(t *testing.T) { + g := setup(t) + + // We need to simulate a page taking a nontrivial amount of time to load in + // order to exercise the "load" event handler of js.WaitLoad. This temporary + // HTTP server lets us simulate a slow external resource which delays the + // page load. + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + time.Sleep(100 * time.Millisecond) + fmt.Fprintf(w, "Hello, World!") + }) + server := http.Server{ + Addr: ":8080", + Handler: serveMux, + } + defer server.Close() + + go func() { + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + panic(err) + } + }() + + // Bootstrap.bundle.min.js sets up a "load" event listener and mutates the + // event by adding the "window" object as the delegateTarget property. If + // we're not careful, we might end up reading that event object from the + // browser as the result of the js.WaitLoad call. If the window object has a + // circular reference, this would cause an "Object reference chain is too + // long" error. Because this crash relies on event handlers firing in a + // specific order we need to try several times in order to have a good shot + // at reproducing it. + file := g.srcFile("fixtures/wait-load-circular-reference.html") + g.page.MustNavigate(file).MustWaitLoad() + g.page.MustNavigate(file).MustWaitLoad() + g.page.MustNavigate(file).MustWaitLoad() +} + func TestPageNavigation(t *testing.T) { g := setup(t) From fd8fd55542b58f5953869a6ebfd54df94cb58d51 Mon Sep 17 00:00:00 2001 From: Joey Bloom <15joeybloom@gmail.com> Date: Mon, 25 Nov 2024 04:27:20 -0600 Subject: [PATCH 2/3] lint --- fixtures/wait-load-circular-reference.html | 10 +++++----- page_test.go | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/fixtures/wait-load-circular-reference.html b/fixtures/wait-load-circular-reference.html index f05d283f..f48b8af6 100644 --- a/fixtures/wait-load-circular-reference.html +++ b/fixtures/wait-load-circular-reference.html @@ -1,11 +1,11 @@ diff --git a/page_test.go b/page_test.go index 92ab20c1..542fdb0e 100644 --- a/page_test.go +++ b/page_test.go @@ -954,15 +954,16 @@ func TestPageWaitLoadCircularReference(t *testing.T) { // HTTP server lets us simulate a slow external resource which delays the // page load. serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + serveMux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { time.Sleep(100 * time.Millisecond) - fmt.Fprintf(w, "Hello, World!") + _, err := fmt.Fprintf(w, "Hello, World!") + g.Err(err) }) server := http.Server{ Addr: ":8080", Handler: serveMux, } - defer server.Close() + defer func() { g.Err(server.Close()) }() go func() { if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { From 426cc38b593a6080aacacb27b23b725465774a35 Mon Sep 17 00:00:00 2001 From: Joey Bloom <15joeybloom@gmail.com> Date: Thu, 28 Nov 2024 12:30:00 -0600 Subject: [PATCH 3/3] update helper.js --- lib/js/helper.go | 2 +- lib/js/helper.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/js/helper.go b/lib/js/helper.go index bc1611f3..55078e52 100644 --- a/lib/js/helper.go +++ b/lib/js/helper.go @@ -117,7 +117,7 @@ var WaitIdle = &Function{ // WaitLoad ... var WaitLoad = &Function{ Name: "waitLoad", - Definition: `function(){const n=this===window;return new Promise((e,t)=>{if(n){if("complete"===document.readyState)return e();window.addEventListener("load",_=>e())}else void 0===this.complete||this.complete?e():(this.addEventListener("load",_=>e()),this.addEventListener("error",t))})}`, + Definition: `function(){const n=this===window;return new Promise((t,e)=>{if(n){if("complete"===document.readyState)return t();window.addEventListener("load",e=>t())}else void 0===this.complete||this.complete?t():(this.addEventListener("load",e=>t()),this.addEventListener("error",e))})}`, Dependencies: []*Function{}, } diff --git a/lib/js/helper.js b/lib/js/helper.js index 6cdb186f..3af573dd 100644 --- a/lib/js/helper.js +++ b/lib/js/helper.js @@ -248,12 +248,12 @@ const functions = { return new Promise((resolve, reject) => { if (isWin) { if (document.readyState === 'complete') return resolve() - window.addEventListener('load', resolve) + window.addEventListener('load', _=>resolve()) } else { if (this.complete === undefined || this.complete) { resolve() } else { - this.addEventListener('load', resolve) + this.addEventListener('load', _=>resolve()) this.addEventListener('error', reject) } }