// Real minified blob worker: both thumbnail sizes, animated originals, // bounded concurrency and graceful fallback, with no page-thread encoding. import assert from 'node:assert/strict'; import puppeteer from './puppeteer.js'; const bundle = await Bun.build({ entrypoints: ['src/image-thumbnails.js'], target: 'browser', minify: true }); assert(bundle.success); const js = await bundle.outputs[0].text(); const server = Bun.serve({ port: 0, fetch: r => new URL(r.url).pathname === '/worker-client.js' ? new Response(js, { headers: { 'Content-Type': 'text/javascript' } }) : new Response('', { headers: { 'Content-Type': 'text/html' } }) }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] }); try { const page = await browser.newPage(); await page.goto(server.url.href); await page.waitForFunction(() => window.thumbnails); const result = await page.evaluate(async () => { const canvas = new OffscreenCanvas(3000,2000), ctx=canvas.getContext('2d'); ctx.fillStyle='red';ctx.fillRect(0,0,3000,2000);ctx.fillStyle='blue';ctx.fillRect(500,0,2000,2000); const blob=await canvas.convertToBlob({type:'image/png'}); const decode = createImageBitmap, encode = OffscreenCanvas.prototype.convertToBlob; let mainCalls=0, pending=0, maxPending=0; const RealWorker = Worker; window.Worker = class extends RealWorker { constructor(...args) { super(...args); this.addEventListener('message',()=>pending--); } postMessage(...args) { pending++;maxPending=Math.max(maxPending,pending);super.postMessage(...args); } }; window.createImageBitmap = () => {mainCalls++;throw Error('main-thread decode');}; OffscreenCanvas.prototype.convertToBlob = () => {mainCalls++;throw Error('main-thread encode');}; const [big, tiles, bad, recovered] = await Promise.all([ thumbnails(blob,{width:1100,tileSize:360}), thumbnails(blob,{tileSize:360}), thumbnails(new Blob(['broken'],{type:'image/jpeg'}),{width:1100}), thumbnails(blob,{width:1100}), ]); const media = await decode(big.media), tile = await decode(big.tile); const square = new OffscreenCanvas(360,360);square.getContext('2d').drawImage(tile,0,0); const pixel=[...square.getContext('2d').getImageData(0,180,1,1).data]; const smallCanvas=new OffscreenCanvas(32,20);smallCanvas.getContext('2d'); const smallBlob=await encode.call(smallCanvas,{type:'image/png'}); const small=await thumbnails(smallBlob,{width:1100,tileSize:360}); const smallTile=await decode(small.tile); const gif=new Blob([Uint8Array.from(atob('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'),c=>c.charCodeAt(0))],{type:'image/gif'}); const animated=await thumbnails(gif,{width:1100,tileSize:360}); const out={mainCalls,maxPending,big:[big.width,big.height],media:[media.width,media.height],tile:[tile.width,tile.height],pixel, tileOnly:!tiles.media&&!!tiles.tile,bad,recovered:!!recovered.media,smallOriginal:small.media===null,smallTile:[smallTile.width,smallTile.height], animated: animated && [animated.width,animated.height,animated.media,animated.tile]}; media.close();tile.close();smallTile.close();return out; }); console.log(result); assert.equal(result.mainCalls,0);assert.equal(result.maxPending,1); assert.deepEqual(result.big,[3000,2000]);assert.deepEqual(result.media,[1100,733]);assert.deepEqual(result.tile,[360,360]); assert(result.pixel[2]>200&&result.pixel[0]<30,'tile crops the center'); assert(result.tileOnly&&result.recovered&&result.smallOriginal);assert.equal(result.bad,null); assert.deepEqual(result.smallTile,[20,20]);assert.deepEqual(result.animated,[1,1,null,null]); const fallback=await browser.newPage();await fallback.goto(server.url.href);await fallback.waitForFunction(()=>window.thumbnails); assert.equal(await fallback.evaluate(async()=>{window.Worker=class {constructor(){throw Error('blocked');}};return thumbnails(new Blob(['x']),{width:1100});}),null); console.log('✓ thumbnails stay off the page thread; sizes, crop, originals, queue, bad-image recovery and worker fallback pass'); } finally { await browser.close();server.stop(true); }