I tried to transfer an image drawn to an offscreen canvas by a web worker to a visible canvas. It works in Chrome, but not in Firefox. No error was thrown in the console, but the visible canvas remains blank. Below is my complete program (index.html
). Is this an issue with how Firefox handles worker and canvas? Is there a fix to this? Thanks!
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener('load', function () {
const w = 600, h = 400;
const blob = new Blob(['(' + render.toString() + ')()'], {type: 'text/javascript'});
const worker = new Worker(URL.createObjectURL(blob));
const offs = document.getElementById('worker').transferControlToOffscreen();
worker.postMessage({ msg: 'load', canvas: offs }, [offs]);
// draw image in offscreen canvas
document.getElementById('render').addEventListener('click', function () {
worker.postMessage({ msg: 'draw' });
});
// transfer image to main canvas
document.getElementById('transfer').addEventListener('click', function () {
const img = document.getElementById('worker');
const ctx = document.getElementById('main').getContext('2d');
ctx.clearRect(0, 0, w, h);
ctx.drawImage(img, 0, 0, w, h, 0, 0, w, h);
});
});
// worker for rendering
function render() {
let canvas;
onmessage = function(e) {
if (e.data.msg === 'load') {
canvas = e.data.canvas;
} else if (e.data.msg === 'draw') {
ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(150, 150, 300, 200);
}
}
}
</script>
</head>
<body>
<div>
<p>
<canvas id="main" width="600" height="400"></canvas>
<canvas id="worker" width="600" height="400" style="display: none"></canvas>
</p>
<p>
<button id="render">Render</button>
<button id="transfer">Transfer</button>
</p>
</div>
</body>
</html>
This is indeed a bug, and I filed BUG 1833496.
For a fix, we'll need to wait a bit, but the last few bugs I opened against their OffscreenCanvas
young implementation were fixed rapidly, so there is hope this one gets fixed too.
If you need a workaround, you could transfer your OffscreenCanvas
to an ImageBitmap
, transfer that back to your main thread through postMessage()
and draw it when needed:
const w = 600, h = 400;
const blob = new Blob(['(' + render.toString() + ')()'], {type: 'text/javascript'});
const worker = new Worker(URL.createObjectURL(blob));
const offs = document.getElementById('worker').transferControlToOffscreen();
worker.postMessage({ msg: 'load', canvas: offs }, [offs]);
// We'll store the canvas bitmap when ready
let source;
worker.onmessage = ({data}) => {
if (data.msg === "bitmap") {
source = data.bmp;
}
};
// draw image in offscreen canvas
document.getElementById('render').addEventListener('click', function () {
worker.postMessage({ msg: 'draw' });
});
// transfer image to main canvas
document.getElementById('transfer').addEventListener('click', function () {
if (!source) {
console.log("no source available yet");
return;
}
const ctx = document.getElementById('main').getContext('2d');
ctx.clearRect(0, 0, w, h);
ctx.drawImage(source, 0, 0, w, h, 0, 0, w, h);
});
// worker for rendering
function render() {
let canvas;
onmessage = function(e) {
if (e.data.msg === 'load') {
canvas = e.data.canvas;
// I removed a "draw" step here, up to you to reimplement as you need
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(150, 150, 300, 200);
// let main thread draw us (https://bugzil.la/1833496)
const bmp = canvas.transferToImageBitmap();
postMessage({msg: "bitmap", bmp}, [bmp]);
}
}
}
<div>
<p>
<button id="render">Render</button>
<button id="transfer">Transfer</button>
</p>
<p>
<canvas id="main" width="600" height="400"></canvas>
<canvas id="worker" width="600" height="400" style="display: none"></canvas>
</p>
</div>