From aaeb7c916e82c60e990492ea691377198e932a66 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 4 Oct 2026 20:51:30 +0200 Subject: [PATCH] build: move the floor to Go 1.27.1; compare goldens by pixels; bump klauspost/compress Go 1.27 chooses different DEFLATE blocks, so the same pixels encode to different PNG bytes. The golden tests now compare decoded pixels (regeneration unchanged). gofmt reformats 23 files under 1.27. govulncheck: no code-affected findings; klauspost/compress v1.18.7 fixes GO-2026-5841. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 2 +- FIDELITY.md | 17 +++++++++++++++++ README.md | 4 ++-- bench/README.md | 2 +- bench/go.mod | 8 ++++---- bench/go.sum | 12 ++++++------ css/external_test.go | 8 ++++---- css/grid.go | 1 - css/gridflex_test.go | 8 ++++---- css/selector_test.go | 4 ++-- css/value_test.go | 10 +++++----- dynamic_test.go | 8 ++++---- engine_test.go | 4 ++-- fixtures_checkbox_test.go | 7 ++++--- fixtures_position_test.go | 34 ++++++++++++++++++++++++++++++++-- go.mod | 4 ++-- go.sum | 4 ++-- images_test.go | 12 ++++++------ js/dynamic_test.go | 16 ++++++++-------- js/js_test.go | 4 ++-- layout/edge17_test.go | 14 +++++++------- layout/features_test.go | 6 +++--- layout/flex17_test.go | 2 +- modules_test.go | 8 ++++---- paint/list_test.go | 4 ++-- 25 files changed, 125 insertions(+), 78 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2fe0ee..b6e5734 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ permissions: env: CGO_ENABLED: "0" - GO_VERSION: "1.26.4" + GO_VERSION: "1.27.1" jobs: test: diff --git a/FIDELITY.md b/FIDELITY.md index 23062d0..4aa0721 100644 --- a/FIDELITY.md +++ b/FIDELITY.md @@ -47,6 +47,23 @@ A survey of how other renderers accelerate loading recommended honouring Retry-A - **Test**: `TestFetchModuleSourceHonoursRetryAfter` serves 429 with `Retry-After: 1` for the first second. With the fix it succeeds after about 1.0s. Stash-verified: the old code drops the module at 0.46s. - **Corpus effect**: none measured. The saved Tailwind, Wikipedia and Hacker News renders are pixel-identical before and after, because no module on those pages was rate-limited. The change corrects a case the corpus does not exercise. +## 2026-10-04 (round 162) — Go 1.27.1 becomes the floor; the upgrade surfaced one real test-harness assumption (golden PNGs compared by encoded bytes), now corrected by pixels; govulncheck clean for the code (engine#262, v0.5.0) + +Upgrade: `go 1.27.1` in `go.mod`, the CI `GO_VERSION`, the README badge and floor, and the bench module. Go 1.27.1 is the final release (1.27.0 and 1.27.1 are published; 1.27 release candidates are not used). + +**Breakages found and what they were** +- **Golden PNGs failed (`TestSVGGolden`, `TestRenderDocumentGolden`)**. The goldens compared the encoded PNG bytes. Go 1.27's DEFLATE chooses different block types, so the same pixels encode to different bytes (2503 vs 2394 bytes). Measured: the SVG golden has 0 differing pixels out of 28000. The golden comparison now accepts the same pixels, decoded; regeneration with `UPDATE_GOLDEN=1` is unchanged. This is a test-harness correction, not a rendering change. +- **gofmt reindents** (the fleet note's first mechanism): a formatting-only change across 23 files, one of which removes a trailing blank line. +- **Coverage**: unchanged floors met (css 99.5, layout 100, paint 100, dom 98.5, paginate 100). + +**Security (govulncheck, Go 1.27.1)** +- Code-affected vulnerabilities: 0. The six standard-library findings that the go1.26.4 pin carried are fixed in the new toolchain. +- Module dependency: `github.com/klauspost/compress` v1.17.4 → v1.18.7 (fix for GO-2026-5841). It is an indirect dependency through the browser HTTP transport. +- `golang.org/x/crypto` v0.57.0 has GO-2026-5932, with no fixed release; the code does not call the affected path. It is recorded here, not worked around. +- The SSRF finding from round 161 stands: the library does not filter destinations, and the README and docs say so. + +**Release**: minor under 0.x, because the Go floor rises to 1.27.1 for every consumer. + ## 2026-10-04 (round 161) — security audit: a 62KB image file could allocate 128MB; raster decode now refuses declared canvases above 25 megapixels; SSRF and toolchain findings recorded (engine#261, patch) Scope: outbound fetches the engine makes for a page (images, sheets, fonts, modules, fetch/XHR, form posts), the decode path for untrusted bytes, the link hit-map, and the Go toolchain (govulncheck). diff --git a/README.md b/README.md index 0d4c796..7f8ad79 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Go Reference](https://pkg.go.dev/badge/github.com/go-webengine/engine.svg)](https://pkg.go.dev/github.com/go-webengine/engine) [![Docs](https://img.shields.io/badge/docs-mkdocs--material-0079A8)](https://go-webengine.github.io/docs/) [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD--3--Clause-blue.svg)](LICENSE) -[![Go 1.26.4+](https://img.shields.io/badge/Go-1.26.4%2B-00ADD8?logo=go)](https://go.dev/dl/) +[![Go 1.27.1+](https://img.shields.io/badge/Go-1.27.1%2B-00ADD8?logo=go)](https://go.dev/dl/) A pure-Go, **`CGO_ENABLED=0`** headless web engine: it fetches a URL, parses the HTML into a DOM, applies a real CSS subset (cascade + inheritance + `var()` + @@ -226,7 +226,7 @@ paint and paginate 100%, dom 97.4%), which CI fails below and which is raised paths (root `engine` package, `cmd/render`) are excluded from the gate because their coverage is not reproducible in CI. The `bench/` fidelity harness is a separate nested module (it pulls chromedp) and is not in the CGO=0 six-arch CI. -`go.mod` floor is `go 1.26.4`; cross-built for all six 64-bit Go targets. +`go.mod` floor is `go 1.27.1`; cross-built for all six 64-bit Go targets. ## Links diff --git a/bench/README.md b/bench/README.md index e6d3e55..e044493 100644 --- a/bench/README.md +++ b/bench/README.md @@ -17,7 +17,7 @@ engine's `go build ./...` / `go test ./...` from the repo root do **not** see it ## Requirements -- Go 1.26.4+ +- Go 1.27.1+ - A Chrome / Chromium binary. Point `CHROME_BIN` at it; default is `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`. diff --git a/bench/go.mod b/bench/go.mod index 5a79abd..304ff31 100644 --- a/bench/go.mod +++ b/bench/go.mod @@ -1,6 +1,6 @@ module github.com/go-webengine/engine/bench -go 1.26.4 +go 1.27.1 // Local parent engine module. Keeps chromedp out of the engine's CGO=0, // 6-arch build and coverage gate. @@ -20,7 +20,7 @@ require ( github.com/breml/rootcerts v0.3.7 // indirect github.com/chromedp/sysutil v1.1.0 // indirect github.com/dlclark/regexp2/v2 v2.5.2 // indirect - github.com/dop251/goja v0.0.0-20260917113740-793a2a65c13b // indirect + github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9 // indirect github.com/evanw/esbuild v0.28.2 // indirect github.com/go-browserhttp/browserhttp v0.2.0 // indirect github.com/go-gfx/gfx v0.34.0 // indirect @@ -29,7 +29,7 @@ require ( github.com/go-images/jpeg2000 v0.1.0 // indirect github.com/go-images/png v0.1.0 // indirect github.com/go-json-experiment/json v0.0.0-20260820222146-c27c302e5fc3 // indirect - github.com/go-opentype/fonts v0.9.1-0.20260907071658-dd9656234a3d // indirect + github.com/go-opentype/fonts v0.10.0 // indirect github.com/go-opentype/opentype v0.13.1-0.20260927180318-ae6327b14eac // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/go-webengine/esbuildsandbox v0.1.0 // indirect @@ -38,7 +38,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/refraction-networking/utls v1.8.2 // indirect github.com/sergeymakinen/go-bmp v1.0.0 // indirect github.com/sergeymakinen/go-ico v1.0.0 // indirect diff --git a/bench/go.sum b/bench/go.sum index bf5762b..20e4bcd 100644 --- a/bench/go.sum +++ b/bench/go.sum @@ -14,8 +14,8 @@ github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipw github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/dlclark/regexp2/v2 v2.5.2 h1:HAsucWRhsqcDzl6Ua9aR8JwYOTzrZyPrF0/FNxJVAI0= github.com/dlclark/regexp2/v2 v2.5.2/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= -github.com/dop251/goja v0.0.0-20260917113740-793a2a65c13b h1:UMDLDHFR1Chu3qnsPNCrVxq0lZgG6JqHpLL5+iqfSkw= -github.com/dop251/goja v0.0.0-20260917113740-793a2a65c13b/go.mod h1:u8yZRUavu+N4EnFFy6J5fVtjE7lEcZ2YyV2GcBXY9c8= +github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9 h1:Ksd3Vil12b/ykT5NjGzd3qNc688cJB9OUXJZZChEktI= +github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9/go.mod h1:u8yZRUavu+N4EnFFy6J5fVtjE7lEcZ2YyV2GcBXY9c8= github.com/evanw/esbuild v0.28.2 h1:A2uETn4jrQTcXaT/shwTDTYBxDjl7fV7nXmUrJxfA2w= github.com/evanw/esbuild v0.28.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= github.com/go-browserhttp/browserhttp v0.2.0 h1:3RcjOQVHgqhSOmI5cfsURpexw/vetoDUoLXKzgTCSac= @@ -34,8 +34,8 @@ github.com/go-images/png v0.1.0 h1:OI4yHotS0lA75sLnG2FTL2Vooh4PWpVWEidre1msSe0= github.com/go-images/png v0.1.0/go.mod h1:V9vaEbr2nrZGorUWn4xv9F24Yuu1/qLVtmailM9GBVo= github.com/go-json-experiment/json v0.0.0-20260820222146-c27c302e5fc3 h1:UADEEmDKgfXbtnGJZ97beY5XLo9ZechG1nlU4KnRrkE= github.com/go-json-experiment/json v0.0.0-20260820222146-c27c302e5fc3/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= -github.com/go-opentype/fonts v0.9.1-0.20260907071658-dd9656234a3d h1:9WvCVDUtnMBVmviCyC65IGN1Sun3k/goiB7wsp/sbjM= -github.com/go-opentype/fonts v0.9.1-0.20260907071658-dd9656234a3d/go.mod h1:b+o8v1zDZ6B3GixA398jGILAh09+WwfyoYz52zKe7DA= +github.com/go-opentype/fonts v0.10.0 h1:9f7ik795SlBEJjz6iqBE/ZV2PaTauAj537MMWw9t4OQ= +github.com/go-opentype/fonts v0.10.0/go.mod h1:b+o8v1zDZ6B3GixA398jGILAh09+WwfyoYz52zKe7DA= github.com/go-opentype/opentype v0.13.1-0.20260927180318-ae6327b14eac h1:mpzfJQe1RlF2zawKqvL5poyNdaV6pMbHkK1h+GVLMuM= github.com/go-opentype/opentype v0.13.1-0.20260927180318-ae6327b14eac/go.mod h1:UjZOTCMtkHheDPzfP0ZghDoQ7wIr6L72T/kuC9Ph1hw= github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= @@ -54,8 +54,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= diff --git a/css/external_test.go b/css/external_test.go index b46f08d..4ed5448 100644 --- a/css/external_test.go +++ b/css/external_test.go @@ -85,11 +85,11 @@ func TestImportURLs(t *testing.T) { []string{"x.css"}, []string{""}}, {` @import url( "spaced.css" ) ;`, []string{"spaced.css"}, []string{""}}, {`body{color:red}`, nil, nil}, - {`@import ;`, nil, nil}, // no clause + {`@import ;`, nil, nil}, // no clause {`@import url(unterminated`, nil, nil}, // no semicolon - {`@import url(;`, nil, nil}, // no closing paren before ; - {`@import badtoken.css;`, nil, nil}, // neither url() nor quoted - {`@import "";`, nil, nil}, // empty url + {`@import url(;`, nil, nil}, // no closing paren before ; + {`@import badtoken.css;`, nil, nil}, // neither url() nor quoted + {`@import "";`, nil, nil}, // empty url {`/* unterminated comment`, nil, nil}, {`@charset "utf-8"`, nil, nil}, // charset without semicolon } diff --git a/css/grid.go b/css/grid.go index db2e1d0..3e6923f 100644 --- a/css/grid.go +++ b/css/grid.go @@ -375,4 +375,3 @@ func applyGridArea(s *Style, v string) { s.GridRowEnd = get(2) s.GridColumnEnd = get(3) } - diff --git a/css/gridflex_test.go b/css/gridflex_test.go index b70b48d..d28c2f4 100644 --- a/css/gridflex_test.go +++ b/css/gridflex_test.go @@ -396,10 +396,10 @@ func TestParseGridTemplateAreas(t *testing.T) { func TestParseGridTemplateAreasRejects(t *testing.T) { for _, v := range []string{ - ``, // no rows - `"a b" "c"`, // ragged - `"unterminated`, // missing close quote - `"" ""`, // zero columns + ``, // no rows + `"a b" "c"`, // ragged + `"unterminated`, // missing close quote + `"" ""`, // zero columns } { if got, ok := parseGridTemplateAreas(v); ok { t.Errorf("areas(%q) unexpectedly ok = %v", v, got) diff --git a/css/selector_test.go b/css/selector_test.go index af23b6a..7dd4395 100644 --- a/css/selector_test.go +++ b/css/selector_test.go @@ -1084,8 +1084,8 @@ func TestAttributeSelectorGitHubDarkModeBug(t *testing.T) { // toggle is :checked. With no user interaction the toggle is unchecked, so the // reveal rule must NOT apply and the menu stays hidden — Chrome's static state. func TestCheckboxHack(t *testing.T) { - hide, _ := parseComplex(".menu") // display:none base rule - reveal, _ := parseComplex("#toggle:checked ~ .menu") // reveal when checked + hide, _ := parseComplex(".menu") // display:none base rule + reveal, _ := parseComplex("#toggle:checked ~ .menu") // reveal when checked toggle := checkbox("toggle", "checkbox", false) menu := el("div", "", "menu") diff --git a/css/value_test.go b/css/value_test.go index 6865174..433691f 100644 --- a/css/value_test.go +++ b/css/value_test.go @@ -121,11 +121,11 @@ func TestParseFontFamily(t *testing.T) { func TestParseFontFamilyKeepsTheNames(t *testing.T) { cases := map[string]string{ `'IBM Plex Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif`: "ibm plex sans,helvetica neue,helvetica,arial", - `Spectral, 'Iowan Old Style', Palatino, Georgia, serif`: "spectral,iowan old style,palatino,georgia", - `"IBM Plex Mono", Menlo, monospace`: "ibm plex mono,menlo", - "sans-serif": "", - "serif, sans-serif": "", - ` Spectral `: "spectral", + `Spectral, 'Iowan Old Style', Palatino, Georgia, serif`: "spectral,iowan old style,palatino,georgia", + `"IBM Plex Mono", Menlo, monospace`: "ibm plex mono,menlo", + "sans-serif": "", + "serif, sans-serif": "", + ` Spectral `: "spectral", } for in, want := range cases { if got := parseFontFamily(in); got.Names != want { diff --git a/dynamic_test.go b/dynamic_test.go index c4288cc..7e457ac 100644 --- a/dynamic_test.go +++ b/dynamic_test.go @@ -79,16 +79,16 @@ func TestLayoutMetricsComputed(t *testing.T) { n := &dom.Node{Type: dom.Element, Tag: "div"} st := &css.Style{ Display: css.DisplayBlock, Position: css.PositionRelative, - Margin: css.Edges{Top: 1, Right: 2, Bottom: 3, Left: 4}, - Padding: css.Edges{Top: 5, Right: 6, Bottom: 7, Left: 8}, - FontSize: 16, FontWeight: 700, Italic: true, + Margin: css.Edges{Top: 1, Right: 2, Bottom: 3, Left: 4}, + Padding: css.Edges{Top: 5, Right: 6, Bottom: 7, Left: 8}, + FontSize: 16, FontWeight: 700, Italic: true, LineHeight: css.LineHeight{Px: 24}, Color: css.Color{R: 1, G: 2, B: 3, A: 255}, Background: css.Color{R: 4, G: 5, B: 6, A: 255}, TextAlign: css.AlignCenter, BoxSizing: css.BorderBox, HasOpacity: true, Opacity: 0.5, ZIndexAuto: false, ZIndex: 7, - Top: css.Length{Px: 10}, Right: css.Length{Auto: true}, + Top: css.Length{Px: 10}, Right: css.Length{Auto: true}, Bottom: css.Length{IsPercent: true, Percent: 0.25}, Left: css.Length{Px: 0}, } box := &layout.Box{Node: n, X: 0, Y: 0, W: 100, H: 50} diff --git a/engine_test.go b/engine_test.go index e41361d..0d7acfc 100644 --- a/engine_test.go +++ b/engine_test.go @@ -164,8 +164,8 @@ func TestRenderDocumentGolden(t *testing.T) { if err != nil { t.Fatalf("read golden (run with UPDATE_GOLDEN=1 to create): %v", err) } - if !bytes.Equal(png, want) { - t.Errorf("render does not match golden fixture.png (%d vs %d bytes)", len(png), len(want)) + if !bytes.Equal(png, want) && !samePixels(t, png, want) { + t.Errorf("render does not match golden fixture.png (pixels differ)") } } diff --git a/fixtures_checkbox_test.go b/fixtures_checkbox_test.go index 6aa56b7..bb25792 100644 --- a/fixtures_checkbox_test.go +++ b/fixtures_checkbox_test.go @@ -13,9 +13,10 @@ import "testing" // Vector-2022 dropdowns. Solid-colour blocks only → font-independent bytes. // // Row layout (body margin 0, blocks stack): -// A hdr y 0..20 green (0,150,0) ; A menu HIDDEN (unchecked) -// B hdr y 20..40 olive (150,150,0) ; B menu SHOWN y 40..70 red (220,40,40) -// C hdr y 70..90 teal (0,150,150) ; C inv HIDDEN (:not(:checked) matches) +// +// A hdr y 0..20 green (0,150,0) ; A menu HIDDEN (unchecked) +// B hdr y 20..40 olive (150,150,0) ; B menu SHOWN y 40..70 red (220,40,40) +// C hdr y 70..90 teal (0,150,150) ; C inv HIDDEN (:not(:checked) matches) func TestCheckboxHackGolden(t *testing.T) { img := renderFixture(t, "checkbox_hack.html", 200, 130) diff --git a/fixtures_position_test.go b/fixtures_position_test.go index 6dd95a8..93b3cc8 100644 --- a/fixtures_position_test.go +++ b/fixtures_position_test.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "image" + stdpng "image/png" "os" "path/filepath" "testing" @@ -58,11 +59,40 @@ func checkGolden(t *testing.T, img *image.RGBA, name string) { if err != nil { t.Fatalf("read golden (run with UPDATE_GOLDEN=1 to create): %v", err) } - if !bytes.Equal(png, want) { - t.Errorf("render does not match golden %s (%d vs %d bytes)", name, len(png), len(want)) + // The contract is the pixels, not the encoder's output: the same pixels + // encode to different bytes when the compressor changes (Go 1.27 picks + // different DEFLATE block types), so compare the decoded images. + if !bytes.Equal(png, want) && !samePixels(t, png, want) { + t.Errorf("render does not match golden %s (pixels differ)", name) } } +func samePixels(t *testing.T, a, b []byte) bool { + t.Helper() + ia, err := stdpng.Decode(bytes.NewReader(a)) + if err != nil { + t.Fatalf("decode render: %v", err) + } + ib, err := stdpng.Decode(bytes.NewReader(b)) + if err != nil { + t.Fatalf("decode golden: %v", err) + } + if ia.Bounds() != ib.Bounds() { + return false + } + bounds := ia.Bounds() + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r1, g1, b1, a1 := ia.At(x, y).RGBA() + r2, g2, b2, a2 := ib.At(x, y).RGBA() + if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 { + return false + } + } + } + return true +} + // TestPositionDemoGolden verifies CSS position relative/absolute/fixed and // z-index stacking with exact painted-pixel invariants (the fixture uses only // solid colour blocks, so its bytes are font-independent and byte-golden-safe). diff --git a/go.mod b/go.mod index 41d2cb3..c727927 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-webengine/engine -go 1.26.4 +go 1.27.1 require ( github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9 @@ -29,7 +29,7 @@ require ( github.com/go-images/png v0.1.0 // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/refraction-networking/utls v1.8.2 // indirect github.com/sergeymakinen/go-bmp v1.0.0 // indirect github.com/sergeymakinen/go-ico v1.0.0 // indirect diff --git a/go.sum b/go.sum index 18a18c1..51ae9ae 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M= diff --git a/images_test.go b/images_test.go index 990c427..1dfae9f 100644 --- a/images_test.go +++ b/images_test.go @@ -17,12 +17,12 @@ import ( func TestCSSImageSize(t *testing.T) { // Intrinsic 900x298 (the go.dev Google wordmark shape). cases := []struct { - name string - st *css.Style - iw, ih int - wantW int - wantH int - wantOK bool + name string + st *css.Style + iw, ih int + wantW int + wantH int + wantOK bool }{ {"nil style keeps intrinsic", nil, 900, 298, 0, 0, false}, {"no CSS dims keeps intrinsic", diff --git a/js/dynamic_test.go b/js/dynamic_test.go index 2165ed6..2ddf280 100644 --- a/js/dynamic_test.go +++ b/js/dynamic_test.go @@ -88,15 +88,15 @@ func TestSessionGeometryReadback(t *testing.T) { mustHaveJS(t, logs, "rect=10,20,200,40,210,60,10,20", "off=200,40,200,40,200,40", - "soff=5,2", // span relative to its offsetParent (the div) - "xoff=0,0,ow=0", // unlaid-out node: zeros, no ancestor rect - "xrect=0", // zero DOMRect - "rects=1,x=0", // d has a rect; x has none + "soff=5,2", // span relative to its offsetParent (the div) + "xoff=0,0,ow=0", // unlaid-out node: zeros, no ancestor rect + "xrect=0", // zero DOMRect + "rects=1,x=0", // d has a rect; x has none "disp=block,w=186px", - "unknown=[]", // unknown computed property → "" - "pri=[],txt=[]", // priority + cssText inert - "has=true,keys=0", // Has true, Keys empty (read-only view) - "dispAfter=block", // setProperty/removeProperty were no-ops + "unknown=[]", // unknown computed property → "" + "pri=[],txt=[]", // priority + cssText inert + "has=true,keys=0", // Has true, Keys empty (read-only view) + "dispAfter=block", // setProperty/removeProperty were no-ops ) } diff --git a/js/js_test.go b/js/js_test.go index eb13b8a..81edd84 100644 --- a/js/js_test.go +++ b/js/js_test.go @@ -566,8 +566,8 @@ func TestElementLangDirTabIndex(t *testing.T) { `) mustHave(t, logs, "lang=fr dir=rtl", - "anchorTab=0", // natively interactive: default 0 - "divTab=-1", // not interactive, no attribute: default -1 + "anchorTab=0", // natively interactive: default 0 + "divTab=-1", // not interactive, no attribute: default -1 "explicitTab=5", "badTab=-1", // unparseable tabindex falls back to the element's default "setTab=3") diff --git a/layout/edge17_test.go b/layout/edge17_test.go index 7de2d44..9c24a0c 100644 --- a/layout/edge17_test.go +++ b/layout/edge17_test.go @@ -264,8 +264,8 @@ func TestGridJustifyStartBorderBoxWidth(t *testing.T) { `
A
` g := findBox(layoutHTML(t, src, 400), "div") a := g.Children[0] - assertF(t, "jbb.A.W", a.W, 60) // border-box width preserved - assertF(t, "jbb.A.X", a.X, 40) // right-aligned: 100-60 + assertF(t, "jbb.A.W", a.W, 60) // border-box width preserved + assertF(t, "jbb.A.X", a.X, 40) // right-aligned: 100-60 } func TestFlexAlignContentFlexStart(t *testing.T) { @@ -327,13 +327,13 @@ func TestGridAreaNameNotFound(t *testing.T) { func TestGridSpanStartVariants(t *testing.T) { cases := []struct { - decl string + decl string wantX, wantW float64 }{ - {"grid-column:span 2", 0, 100}, // span-only start, auto-placed - {"grid-column:span 1 / 3", 50, 50}, // span 1 + end line - {"grid-column:span 3 / 2", 0, 150}, // span clamped to start 0 - {"grid-column-end:span 1", 0, 50}, // end span of 1 + {"grid-column:span 2", 0, 100}, // span-only start, auto-placed + {"grid-column:span 1 / 3", 50, 50}, // span 1 + end line + {"grid-column:span 3 / 2", 0, 150}, // span clamped to start 0 + {"grid-column-end:span 1", 0, 50}, // end span of 1 } for _, c := range cases { src := `
` + diff --git a/layout/features_test.go b/layout/features_test.go index c9ff561..894fb59 100644 --- a/layout/features_test.go +++ b/layout/features_test.go @@ -34,7 +34,7 @@ func TestMarginPercentShorthandNarrowsBoxSymmetrically(t *testing.T) { // with body margin 0 so cw is exactly 1024. src := `
x
` div := findBox(layoutHTML(t, src, 1024), "div") - assertF(t, "percent-margin.X", div.X, 102.4) // 1024 * 0.10 + assertF(t, "percent-margin.X", div.X, 102.4) // 1024 * 0.10 assertF(t, "percent-margin.ContentW", div.ContentW, 819.2) // 1024 - 2*102.4 } @@ -44,7 +44,7 @@ func TestMarginPercentLonghandsIndependently(t *testing.T) { // the shorthand (applyMarginShorthand). src := `
x
` div := findBox(layoutHTML(t, src, 1000), "div") - assertF(t, "percent-longhand.X", div.X, 250) // 1000 * 0.25 + assertF(t, "percent-longhand.X", div.X, 250) // 1000 * 0.25 assertF(t, "percent-longhand.ContentW", div.ContentW, 700) // 1000 - 250 - 50 } @@ -56,7 +56,7 @@ func TestMarginPercentLeftAutoRightMixed(t *testing.T) { // pre-existing precedence. src := `
x
` div := findBox(layoutHTML(t, src, 1000), "div") - assertF(t, "mixed.X", div.X, 100) // 1000 * 0.10 + assertF(t, "mixed.X", div.X, 100) // 1000 * 0.10 assertF(t, "mixed.ContentW", div.ContentW, 200) // explicit width, unaffected } diff --git a/layout/flex17_test.go b/layout/flex17_test.go index bebb665..2a5865a 100644 --- a/layout/flex17_test.go +++ b/layout/flex17_test.go @@ -99,7 +99,7 @@ func TestFlexRowGapWithWrap(t *testing.T) { `
C
` outer := findBox(layoutHTML(t, src, 400), "div") assertF(t, "rgap.C.Y", outer.Children[2].Y, 30) // 20 line + 10 gap - assertF(t, "rgap.container.H", outer.H, 50) // 20 + 10 + 20 + assertF(t, "rgap.container.H", outer.H, 50) // 20 + 10 + 20 } func TestFlexGapShorthandTwoValues(t *testing.T) { diff --git a/modules_test.go b/modules_test.go index f8c778d..5ca9e97 100644 --- a/modules_test.go +++ b/modules_test.go @@ -272,12 +272,12 @@ func TestResolveModuleSpecifier(t *testing.T) { importer, spec, want string ok bool }{ - {"", "https://cdn.ex/x.js", "https://cdn.ex/x.js", true}, // absolute - {"", "/packs/a.js", "https://ex.com/packs/a.js", true}, // root-relative vs page + {"", "https://cdn.ex/x.js", "https://cdn.ex/x.js", true}, // absolute + {"", "/packs/a.js", "https://ex.com/packs/a.js", true}, // root-relative vs page {"https://ex.com/packs/a.js", "./b.js", "https://ex.com/packs/b.js", true}, // relative vs importer {"https://ex.com/packs/sub/a.js", "../c.js", "https://ex.com/packs/c.js", true}, - {"", "react", "", false}, // bare specifier - {"", "", "", false}, // empty + {"", "react", "", false}, // bare specifier + {"", "", "", false}, // empty } for _, c := range cases { got, ok := resolveModuleSpecifier(page, c.importer, c.spec) diff --git a/paint/list_test.go b/paint/list_test.go index 4791385..6d10f1f 100644 --- a/paint/list_test.go +++ b/paint/list_test.go @@ -77,8 +77,8 @@ func TestPaintCircleMarker(t *testing.T) { func TestPaintDecimalMarker(t *testing.T) { img := paintMarkerOnly(48, 24, &layout.Marker{ Type: css.ListDecimal, Text: "1.", - Style: &css.Style{FontFamily: css.Sans, FontSize: 16, FontWeight: 400, Color: markerBlack}, - X: 12, Y: 2, Ascent: 12, + Style: &css.Style{FontFamily: css.Sans, FontSize: 16, FontWeight: 400, Color: markerBlack}, + X: 12, Y: 2, Ascent: 12, }) // Some ink lands in the glyph region. ink := 0