From f3911b1b5e65b13540b7b1844bfc8057c9e2a73e Mon Sep 17 00:00:00 2001 From: doodlewind <7312949+doodlewind@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:51:55 +0000 Subject: [PATCH] fix(editor): discard stale layout bitmap in turbo renderer (#10427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes this bug caused by stale bitmap: [Screen Recording 2025-02-24 at 6.10.19 PM.mov (uploaded via Graphite) ](https://app.graphite.dev/media/video/lEGcysB4lFTEbCwZ8jMv/3e24f4b7-6f95-4c7c-a79a-b8e4ffdb3b10.mov) --- .../src/viewport-renderer/painter.worker.ts | 12 ++++++++---- .../src/viewport-renderer/viewport-renderer.ts | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/blocksuite/affine/shared/src/viewport-renderer/painter.worker.ts b/blocksuite/affine/shared/src/viewport-renderer/painter.worker.ts index a2cb78f61a..86cd795088 100644 --- a/blocksuite/affine/shared/src/viewport-renderer/painter.worker.ts +++ b/blocksuite/affine/shared/src/viewport-renderer/painter.worker.ts @@ -8,6 +8,7 @@ type WorkerMessagePaint = { height: number; dpr: number; zoom: number; + version: number; }; }; @@ -63,7 +64,7 @@ class LayoutPainter { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } - paint(layout: ViewportLayout) { + paint(layout: ViewportLayout, version: number) { const { canvas, ctx } = this; if (!canvas || !ctx) return; if (layout.rect.w === 0 || layout.rect.h === 0) { @@ -103,7 +104,10 @@ class LayoutPainter { }); const bitmap = canvas.transferToImageBitmap(); - self.postMessage({ type: 'bitmapPainted', bitmap }, { transfer: [bitmap] }); + self.postMessage( + { type: 'bitmapPainted', bitmap, version }, + { transfer: [bitmap] } + ); } } @@ -127,9 +131,9 @@ self.onmessage = async (e: MessageEvent) => { switch (type) { case 'paintLayout': { - const { layout, width, height, dpr, zoom } = data; + const { layout, width, height, dpr, zoom, version } = data; painter.setSize(width, height, dpr, zoom); - painter.paint(layout); + painter.paint(layout, version); break; } } diff --git a/blocksuite/affine/shared/src/viewport-renderer/viewport-renderer.ts b/blocksuite/affine/shared/src/viewport-renderer/viewport-renderer.ts index 2c2eb6b057..c62a8f0a6a 100644 --- a/blocksuite/affine/shared/src/viewport-renderer/viewport-renderer.ts +++ b/blocksuite/affine/shared/src/viewport-renderer/viewport-renderer.ts @@ -31,6 +31,7 @@ const zoomThreshold = 1; export class ViewportTurboRendererExtension extends LifeCycleWatcher { state: 'monitoring' | 'paused' = 'paused'; disposables = new DisposableGroup(); + private layoutVersion = 0; static override setup(di: Container) { di.addImpl(ViewportTurboRendererIdentifier, this, [StdIdentifier]); @@ -115,6 +116,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher { } invalidate() { + this.layoutVersion++; this.layoutCache = null; this.clearTile(); this.clearCanvas(); // Should clear immediately after content updates @@ -137,6 +139,8 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher { if (!this.worker) return; const dpr = window.devicePixelRatio; + const currentVersion = this.layoutVersion; + this.worker.postMessage({ type: 'paintLayout', data: { @@ -145,12 +149,18 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher { height: layout.rect.h, dpr, zoom: this.viewport.zoom, + version: currentVersion, }, }); this.worker.onmessage = (e: MessageEvent) => { if (e.data.type === 'bitmapPainted') { - this.handlePaintedBitmap(e.data.bitmap, resolve); + if (e.data.version === this.layoutVersion) { + this.handlePaintedBitmap(e.data.bitmap, resolve); + } else { + e.data.bitmap.close(); + resolve(); + } } }; }); @@ -180,7 +190,9 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher { } private drawCachedBitmap(layout: ViewportLayout) { - const bitmap = this.tile!.bitmap; + if (!this.tile) return; // version mismatch + + const bitmap = this.tile.bitmap; const ctx = this.canvas.getContext('2d'); if (!ctx) return;