Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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)