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;