Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions element_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rod_test

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -10,6 +11,7 @@ import (
"net"
"os"
"path/filepath"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -709,6 +711,100 @@ func TestWaitStableRAP(t *testing.T) {
g.Err(el.WaitStableRAF())
}

func TestWaitRepaintHonorsPageContext(t *testing.T) {
g := setup(t)
ctx, cancel := context.WithCancel(g.page.GetContext())
cancel()

err := g.page.Context(ctx).WaitRepaint()
if !errors.Is(err, context.Canceled) {
t.Fatalf("WaitRepaint error = %v, want context.Canceled", err)
}
}

const waitStableRAFDemoPage = `<!doctype html>
<style>#target { width: 80px; height: 30px; }</style>
<button id="target">target</button>
<script>
let x = 0;
function move() {
document.getElementById('target').style.transform = 'translateX(' + (x++) + 'px)';
requestAnimationFrame(move);
}
requestAnimationFrame(move);
</script>`

func TestWaitStableRAFOnBackgroundPageHonorsContext(t *testing.T) {
g := setup(t)
target := g.newPage(g.html(waitStableRAFDemoPage)).MustWaitLoad()
other := g.newPage(g.blank()).MustWaitLoad()
other.MustActivate()

// Freeze the hidden target so requestAnimationFrame cannot resolve. The
// operation must still return when its caller context expires.
if err := (proto.PageSetWebLifecycleState{State: proto.PageSetWebLifecycleStateStateFrozen}).Call(target); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
errCh := make(chan error, 1)
go func() {
errCh <- target.MustElement("#target").Context(ctx).WaitStableRAF()
}()
select {
case err := <-errCh:
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("background WaitStableRAF error = %v, want context deadline exceeded", err)
}
case <-time.After(1 * time.Second):
t.Fatal("background WaitStableRAF remained blocked; this reproduces the pre-fix root-context regression")
}
}

func TestWaitStableRAFSurvivesFrequentActivationSwitches(t *testing.T) {
g := setup(t)
target := g.newPage(g.html(waitStableRAFDemoPage)).MustWaitLoad()
other := g.newPage(g.blank()).MustWaitLoad()

ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)
defer cancel()
errCh := make(chan error, 1)
go func() {
errCh <- target.MustElement("#target").Context(ctx).WaitStableRAF()
}()

done := make(chan struct{})
var switchers sync.WaitGroup
switchers.Add(1)
go func() {
defer switchers.Done()
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
_, _ = target.Activate()
_, _ = other.Activate()
case <-done:
return
}
}
}()
defer func() {
close(done)
switchers.Wait()
}()

select {
case err := <-errCh:
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("WaitStableRAF during activation switches error = %v, want context deadline exceeded", err)
}
case <-time.After(2 * time.Second):
t.Fatal("WaitStableRAF remained blocked during frequent activation switches")
}
}

func TestCanvasToImage(t *testing.T) {
g := setup(t)

Expand Down
5 changes: 4 additions & 1 deletion page.go
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,10 @@ func (p *Page) WaitIdle(timeout time.Duration) (err error) {
// Doc: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
func (p *Page) WaitRepaint() error {
// we use root here because iframe doesn't trigger requestAnimationFrame
_, err := p.root.Eval(`() => new Promise(r => requestAnimationFrame(r))`)
// but the root evaluation must still inherit the caller's context. Otherwise
// Page.Context/Timeout cannot cancel this wait when the root page is hidden
// or keeps repainting indefinitely.
_, err := p.root.Context(p.ctx).Eval(`() => new Promise(r => requestAnimationFrame(r))`)
return err
}

Expand Down
Loading