From 8a3634f5a437f505055a7c31510ab7dd5bddbac9 Mon Sep 17 00:00:00 2001 From: DeepWiki Dev Date: Sat, 20 Jun 2026 19:41:24 -0400 Subject: [PATCH 1/2] Preserve canvas pixels in rendered clones Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- browser/canvas.go | 35 ++++++++++++++++++++++++++++ browser/pool.go | 2 +- browser/pool_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 browser/canvas.go diff --git a/browser/canvas.go b/browser/canvas.go new file mode 100644 index 0000000..fd899d8 --- /dev/null +++ b/browser/canvas.go @@ -0,0 +1,35 @@ +package browser + +import "github.com/go-rod/rod" + +func snapshotHTML(page *rod.Page) (string, error) { + if err := snapshotCanvasPixels(page); err != nil { + return "", err + } + return page.HTML() +} + +func snapshotCanvasPixels(page *rod.Page) error { + _, err := page.Eval(`() => { + const maxCanvasSnapshotPixels = 4096 * 4096; + for (const canvas of document.querySelectorAll("canvas")) { + try { + if (canvas.width === 0 || canvas.height === 0) continue; + if (canvas.width * canvas.height > maxCanvasSnapshotPixels) continue; + const src = canvas.toDataURL("image/png"); + if (!src.startsWith("data:image/png")) continue; + const img = document.createElement("img"); + for (const attr of canvas.attributes) img.setAttribute(attr.name, attr.value); + img.setAttribute("src", src); + if (!img.hasAttribute("alt")) img.setAttribute("alt", ""); + if (!img.hasAttribute("width")) img.setAttribute("width", String(canvas.width)); + if (!img.hasAttribute("height")) img.setAttribute("height", String(canvas.height)); + const rect = canvas.getBoundingClientRect(); + if (!img.style.width && rect.width > 0) img.style.width = rect.width + "px"; + if (!img.style.height && rect.height > 0) img.style.height = rect.height + "px"; + canvas.replaceWith(img); + } catch (_) {} + } + }`) + return err +} diff --git a/browser/pool.go b/browser/pool.go index 5a8d289..80293c6 100644 --- a/browser/pool.go +++ b/browser/pool.go @@ -139,7 +139,7 @@ func (p *Pool) Render(ctx context.Context, rawURL string) (RenderResult, error) settle(page, p.opts.Settle) } - html, err := page.HTML() + html, err := snapshotHTML(page) if err != nil { return RenderResult{}, fmt.Errorf("serialise %s: %w", rawURL, err) } diff --git a/browser/pool_test.go b/browser/pool_test.go index 4c14a78..8b1b2ab 100644 --- a/browser/pool_test.go +++ b/browser/pool_test.go @@ -2,7 +2,9 @@ package browser import ( "context" + "encoding/base64" "errors" + "image/png" "net/http" "net/http/httptest" "os" @@ -126,6 +128,58 @@ func TestRenderCapturesFinalDOM(t *testing.T) { } } +func TestRenderPreservesCanvasPixels(t *testing.T) { + if testing.Short() { + t.Skip("render test drives Chrome; skipped under -short") + } + if _, ok := LookChrome(); !ok { + t.Skip("no Chrome/Chromium found; skipping render test") + } + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write([]byte(` + + +`)) + })) + defer srv.Close() + + p := New(Options{Headless: true, Workers: 1, Settle: 300 * time.Millisecond, RenderTimeout: 20 * time.Second}) + defer func() { _ = p.Close() }() + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + res, err := p.Render(ctx, srv.URL) + if err != nil { + t.Fatalf("render: %v", err) + } + if !strings.Contains(res.HTML, `src="data:image/png;base64,`) { + t.Fatalf("rendered HTML does not include a canvas PNG snapshot:\n%s", res.HTML) + } + data, ok := strings.CutPrefix(res.HTML[strings.Index(res.HTML, `src="data:image/png;base64,`):], `src="data:image/png;base64,`) + if !ok { + t.Fatalf("rendered HTML does not include a canvas PNG snapshot:\n%s", res.HTML) + } + encoded, _, ok := strings.Cut(data, `"`) + if !ok { + t.Fatalf("canvas PNG snapshot is not quoted correctly:\n%s", res.HTML) + } + img, err := png.Decode(base64.NewDecoder(base64.StdEncoding, strings.NewReader(encoded))) + if err != nil { + t.Fatalf("decode canvas PNG snapshot: %v", err) + } + r, g, b, a := img.At(0, 0).RGBA() + if r < 0xff00 || g > 0x0100 || b > 0x0100 || a < 0xff00 { + t.Fatalf("canvas PNG first pixel = rgba(%#x, %#x, %#x, %#x); want opaque red", r, g, b, a) + } +} + func TestIsHTML(t *testing.T) { cases := []struct { ct string From 943e1193d6ec8a71741a645f097429687934e676 Mon Sep 17 00:00:00 2001 From: DeepWiki Dev Date: Sun, 21 Jun 2026 01:07:17 -0400 Subject: [PATCH 2/2] Make AppDir executable assertion POSIX-only Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- pack/appdir_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pack/appdir_test.go b/pack/appdir_test.go index ee9916f..2db4a7a 100644 --- a/pack/appdir_test.go +++ b/pack/appdir_test.go @@ -5,6 +5,7 @@ import ( "image/png" "os" "path/filepath" + "runtime" "strings" "testing" ) @@ -60,7 +61,7 @@ func TestBuildAppDir(t *testing.T) { if string(tr[:8]) != trailerMagic { t.Error("AppRun missing the trailer") } - if info, _ := os.Stat(apprun); info.Mode().Perm()&0o111 == 0 { + if info, _ := os.Stat(apprun); runtime.GOOS != "windows" && info.Mode().Perm()&0o111 == 0 { t.Error("AppRun is not executable") }