Frontmatter
| id | 9479 |
| title | Firefox Nightly R&D: OffscreenCanvas Worker-to-Worker Transfer Failure |
| state | Closed |
| labels | bugaiarchitecture |
| assignees | tobiu |
| createdAt | Mar 15, 2026, 4:19 PM |
| updatedAt | Mar 15, 2026, 9:48 PM |
| githubUrl | https://github.com/neomjs/neo/issues/9479 |
| author | tobiu |
| commentsCount | 4 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Mar 15, 2026, 5:03 PM |
Firefox Nightly R&D: OffscreenCanvas Worker-to-Worker Transfer Failure

Input from Gemini 3.1 Pro:
✦ Implementation Blueprint:
The fix requires changes across three distinct realms: Main, App Worker, and Canvas Worker.
Main Thread (
src/main/DomAccess.mjs):
- Currently,
getOffscreenCanvasextracts the canvas and returns it to the caller (App Worker).- We need a new method (e.g.,
transferCanvasToWorker(data)) or an enhancement togetOffscreenCanvas.- It must extract the canvas, and then use
Neo.worker.Manager.sendMessage('canvas', ...)to send the node directly to the Canvas Worker, bypassing the App Worker entirely.Canvas Worker (
src/worker/Canvas.mjs&src/canvas/Base.mjs):
- The Canvas worker needs to receive this direct message from Main.
Neo.worker.Canvas.registerCanvasis currently a remote method called by App. We might need a direct message handler from Main to populateme.canvasWindowMap[data.nodeId][data.windowId].- Once received and registered, it needs to ping the App Worker back (using their direct
MessageChannelport) to say "I have the canvas, you can proceed."App Worker (
src/component/Canvas.mjs):
- The
afterSetMountedloop currently callsNeo.main.DomAccess.getOffscreenCanvas, awaits it, and then callsNeo.worker.Canvas.registerCanvas.- This logic must change: a) Tell Main to transfer the canvas to the Canvas Worker. b)
awaita ping/promise from the Canvas Worker confirming receipt. c) Then setme.offscreenRegistered = true.

Input from Gemini 3.1 Pro:
✦ Implementation completed and verified across Chrome, Firefox Nightly, and Safari.
A pull request has been submitted: #9480
The solution successfully implements the Triangular Communication pattern as planned:
Neo.component.Canvasnow pauses its registration sequence and waits for a direct callback.Neo.main.DomAccess#transferCanvasToWorkerextracts the canvas and sends it directly to the Canvas Worker.Neo.worker.Canvas#registerCanvasDirectreceives the payload and pings the App Worker viaaction: 'canvasRegistered'.Neo.worker.App#onCanvasRegisteredreceives the ping and resolves the component's promise.The failed unit test in
StoreFilterProfile.spec.mjsis an unrelated performance benchmark timeout and not caused by these changes.

before:
after:

created a bugreport for the mozilla team here: https://bugzilla.mozilla.org/show_bug.cgi?id=2023457
Browser: Firefox Nightly (v150) Context: Multi-threaded architecture (
SharedWorker/Worker)The Problem: OffscreenCanvas elements render perfectly in Chrome and Safari, but fail to appear in Firefox Nightly.
During our investigation, we found that transferring an
OffscreenCanvasfrom the Main Thread to the App Worker, and then attempting to transfer it again from the App Worker to the Canvas Worker (viaMessageChannel), fails silently inSharedWorkermode.However, when switching to Dedicated Workers, Firefox throws this explicit internal error:
NotSupportedError: Cannot transfer OffscreenCanvas bound to element using captureStream.We are not using
captureStream. This indicates a core bug or architectural restriction in Firefox where it refuses to support chained or "Worker-to-Worker" transfers ofOffscreenCanvasobjects.Because the
OffscreenCanvasis included in thetransferarray of theMessageChannel.postMessagecall, Firefox aborts the entire message serialization silently (in SharedWorkers). This completely severs theregisterCanvascommunication between the App Worker and Canvas Worker.Proposed Architectural Fix (Triangular Communication): We need to bypass the App Worker as the middleman for the transfer buffer.
transferControlToOffscreen().dest: 'canvas').MessageChannel(which is proven to work for VDom/Data when no canvas buffer is in the payload).This R&D effort will determine if FF Nightly even supports transferring a canvas to a SharedWorker from Main, as the current silent failure leaves that ambiguous.