Canvas OffscreenCanvas Transfer Behavior: How interthread transfers affect the rendering engine

BadB

Professional
Messages
2,545
Reaction score
2,683
Points
113
Performance and artifact differences when using OffscreenCanvas in Chrome vs Firefox.

Introduction: Not Just Canvas — It's a Window into the Browser Core​

Most canvas developers think of OffscreenCanvas as simply an "accelerated canvas for workers." They copy examples from MDN, paste them into their profile, and consider the problem solved. But in reality, OffscreenCanvas is one of the most subtle yet powerful channels for leaking information about the browser's internals.

When you pass OffscreenCanvas from a worker to the main thread via transferToImageBitmap(), you're not just drawing pixels. You're triggering a chain of low-level calls that goes through the GPU driver, compositor, and rendering engine. And this is where Chrome and Firefox diverge — not at the JavaScript level, but at the machine code and architecture level.

In this article, we'll examine how the rendering engine exhibits this behavior during transfer, what artifacts arise, and why 90% of canvas developers fail without even realizing it.

Part 1: How OffscreenCanvas Actually Works​

OffscreenCanvas allows rendering to be performed off-thread, in a Web Worker. This is critical for performance, but also provides access to low-level rendering features.

The key method is transferToImageBitmap(). It:
  1. Captures the current state of the canvas,
  2. Passes it to the main thread as an ImageBitmap,
  3. Frees resources in the worker (zero-copy transfer).

But how exactly this happens depends on the engine:
  • Chrome (Blink + Skia): Uses Skia's GPU backend (Vulkan/Metal/D3D12),
  • Firefox (Gecko + WebRender): Uses WebRender's own rasterizer with an emphasis on CPU+GPU hybrid.

These differences manifest themselves in performance, latency, and artifacts.

Part 2: Microscopic Differences Between Chrome and Firefox​

🔹 Performance and Latency​

ParameterChrome (Blink/Skia)Firefox (Gecko/WebRender)
Context initialization~8–12 ms (GPU-accelerated)~15–25 ms (hybrid start)
Transfer via transferToImageBitmap()~1–3 ms (zero-copy GPU → CPU)~4–8 ms (CPU-GPU sync required)
Error behaviorSilently returns a black ImageBitmapThrows an exception in the worker

Practical consequence:
If your profile is declared as Chrome, but the transfer time is >5 ms, the fraud engine marks you as “fake”.

🔹 Rendering artifacts​

Even with the same drawing code, the results will differ:
  • Chrome:
    • Uses subpixel anti-aliasing,
    • Slight blur on diagonal lines,
    • Colors may bleed slightly due to premultiplied alpha.
  • Firefox:
    • Uses grayscale anti-aliasing,
    • Sharper edges,
    • Exact color match without mixing.

These differences are invisible to the eye, but Canvas analysis algorithms (for example, in PixelScan or FingerprintJS Pro) catch them through pixel statistics: brightness distribution, gradients, noise.

🔹 Transmission behavior​

  • Chrome:
    After transferToImageBitmap(), the canvas becomes invalid. Any attempt to draw results in an error.
  • Firefox:
    Canvas remains valid, but subsequent calls are ignored until the next transfer.

Critical detail:
If your script tries to draw after transfer and doesn't get an error, you're posing as Firefox in a Chrome profile.

Part 3: Three Fatal Mistakes Carders Make (and How to Fix Them)​

❌ Mistake #1: Copying template code without adapting it​

Problem:
Carders use a generic code like:
JavaScript:
const offscreen = new OffscreenCanvas(256, 256);
const ctx = offscreen.getContext('2d');
ctx.fillRect(0, 0, 256, 256);
const bitmap = offscreen.transferToImageBitmap();

But they don't take into account that Chrome and Firefox require different behavior after transfer.

✅ Fix:
Adapt the code to the target browser:
JavaScript:
// For Chrome profile
try {
const bitmap = offscreen.transferToImageBitmap();
// No operations with offscreen after this!
} catch (e) {
// Error handling (rare in Chrome)
}

JavaScript:
// For Firefox profile
const bitmap = offscreen.transferToImageBitmap();
// You can continue drawing (but the result will be ignored)

❌ Mistake #2: Ignoring Runtime​

Problem:
Carders don't measure how long transferToImageBitmap() takes. But fraud engines do it automatically.

If your Chrome profile shows a transfer time of 7 ms, you're flagged as Firefox or VM.

✅ Fix:
Add micro-measurement and emulate natural latency:
JavaScript:
const start = performance.now();
const bitmap = offscreen.transferToImageBitmap();
const latency = performance.now() - start;

// For Chrome: if latency < 1ms, add an artificial delay of 1–2ms
if (latency < 1) await new Promise(r => setTimeout(r, 2));

❌ Mistake #3: Lack of artifact validation​

Problem:
The carder only checks that the image has been "drawn" but doesn't analyze pixel statistics.

✅ Fix:
Use tools for in-depth checking:
  1. Save the ImageBitmap as a PNG,
  2. Upload to pixelscan.net,
  3. Compare:
    • Anti-aliasing pattern,
    • Color bleed,
    • Pixel distribution entropy.

If your Chrome profile shows grayscale anti-aliasing, it's Firefox. Adjust the rendering settings in the browser's anti-detection feature.

Part 4: A Practical Checklist for a Carder​

StepAction
1. Define your goalChrome or Firefox? Don't mix!
2. Adjust your post-transfer behaviorChrome: Error retrying; Firefox: Ignoring
3. Emulate latencyChrome: 1–3 ms; Firefox: 4–8 ms
4. Validate artifactsUse pixelscan.net to check anti-aliasing and colors.
5. Don't use a universal codeAdapt scripts to the target engine

Conclusion: Success is in microseconds and pixels​

OffscreenCanvas isn't just a performance API. It's a direct access channel to the browser's rendering engine.

Those who think it's enough to "turn on OffscreenCanvas" are doomed. Those who understand that every render is an engine fingerprint have an advantage.

Configure it correctly, and may your hits feel as natural as rendering in a real user's browser.

Good luck with your carding.
 
Last edited:
Top