vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Draw the celebration on the main thread, not in a blocked worker
6b7d6a1897dca7a8f42669a02c3f6981192ccd22 · Keenan Nemetz · 13d ago · parent 0f8054ad
Draw the celebration on the main thread, not in a blocked worker The burst never painted. canvas-confetti's default export is built with useWorker: true, and it builds that worker from a blob: URL, which this page's CSP (default-src 'self', no worker-src) blocks. The library does not fall back: it transfers its canvas to the worker with transferControlToOffscreen and then posts to it, so the canvas is created, is full-size, is permanently empty, and reports no error. reload, shared link and bookmark behaviour, and the address-bar strip, all worked — which is why the flag looked handled and the burst looked absent. Creating our own instance with useWorker: false draws on the main thread, where 120 particles for ~2s cost nothing, and needs no CSP change. Widening the policy with worker-src blob: to keep the offscreen path would trade a real security boundary for a party trick. Verified by clicking Merge in a real Firefox against a scratch instance: the canvas has 4148 non-transparent pixels and no transferred context. The same probe against the old build reports getContext failing with InvalidStateError.
server/ui_test.go +8
| @@ -105,6 +105,14 @@ func TestConfettiIsFetchedOnlyForALanding(t *testing.T) { | |||
| 105 | 105 | if n := strings.Count(app, "/static/confetti.browser.js"); n != 1 { | |
| 106 | 106 | t.Fatalf("app.js names the confetti asset %d times, want 1", n) | |
| 107 | 107 | } | |
| 108 | // The library's default export offloads to a Web Worker built from a blob: | ||
| 109 | // URL, and default-src 'self' blocks it; the canvas is transferred to the | ||
| 110 | // blocked worker first, so the burst silently draws nothing. Only a real | ||
| 111 | // browser can see that, so what this test can hold is the choice that avoids | ||
| 112 | // it: our own instance, on the main thread. | ||
| 113 | if !strings.Contains(app, "useWorker: false") { | ||
| 114 | t.Fatalf("app.js does not create a main-thread confetti instance:\n%s", app) | ||
| 115 | } | ||
| 108 | 116 | for _, page := range []string{"/", "/r/" + f.repo + "/requests"} { | |
| 109 | 117 | if code, html := f.get(page); code != 200 { | |
| 110 | 118 | t.Fatalf("%s = %d", page, code) | |