fix: avoid forced layout in renderInfo by using canvas.height#9304
fix: avoid forced layout in renderInfo by using canvas.height#9304christian-byrne merged 3 commits intomainfrom
Conversation
🎭 Playwright: ✅ 561 passed, 0 failed · 3 flaky📊 Browser Reports
|
🎨 Storybook: ✅ Built — View Storybook |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughrenderInfo now defaults its y coordinate to the canvas height divided by devicePixelRatio instead of using offsetHeight. A new unit test file was added that mocks CanvasRenderingContext2D and a canvas to verify renderInfo's coordinate computation and context calls. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
📦 Bundle: 4.62 MB gzip 🔴 +36 BDetailsSummary
Category Glance App Entry Points — 17.7 kB (baseline 17.7 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.02 MB (baseline 1.02 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 72.3 kB (baseline 72.3 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed Panels & Settings — 453 kB (baseline 453 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed User & Accounts — 16 kB (baseline 16 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed Editors & Dialogs — 78.2 kB (baseline 78.2 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 2 added / 2 removed UI Components — 56.6 kB (baseline 56.6 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed Data & Services — 2.8 MB (baseline 2.8 MB) • 🔴 +70 BStores, services, APIs, and repositories
Status: 15 added / 15 removed Utilities & Hooks — 60.5 kB (baseline 60.5 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 11 added / 11 removed Vendor & Third-Party — 8.9 MB (baseline 8.9 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Other — 8.16 MB (baseline 8.16 MB) • ⚪ 0 BBundles that do not match a named category
Status: 51 added / 51 removed |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/litegraph/src/LGraphCanvas.renderInfo.test.ts`:
- Around line 45-53: The test temporarily overwrites window.devicePixelRatio but
only restores it after assertions, which can leak state if the assertion fails;
wrap the change around lgCanvas.renderInfo(ctx, 10, 0) in a try/finally: save
originalDPR, set devicePixelRatio to 2 before calling lgCanvas.renderInfo(...)
in the try block, and always restore the originalDPR with
Object.defineProperty(window, 'devicePixelRatio', { value: originalDPR,
configurable: true }) in the finally block so DPR is reliably reset even on test
failures.
In `@src/lib/litegraph/src/LGraphCanvas.ts`:
- Line 5212: The code computes Y using window.devicePixelRatio which can be
wrong for canvases in other windows; replace the global window DPR lookup with
the canvas owner window DPR (use
this.canvas.ownerDocument.defaultView.devicePixelRatio with a fallback of 1)
when computing y (the expression around y = y || this.canvas.height /
(window.devicePixelRatio || 1) - 80). Ensure you null-check
ownerDocument/defaultView (or use optional chaining) and keep the same fallback
behavior so the info panel Y position uses the canvas's document window DPR.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/lib/litegraph/src/LGraphCanvas.renderInfo.test.tssrc/lib/litegraph/src/LGraphCanvas.ts
⚡ Performance Report
Raw data{
"timestamp": "2026-03-13T15:17:06.923Z",
"gitSha": "2d383bb581e5e7b221f69cbc01d9872ba1fdba9a",
"branch": "perf/fix-render-info",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2031.4359999999851,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.329999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 404.357,
"heapDeltaBytes": 1579760
},
{
"name": "canvas-idle",
"durationMs": 2027.0000000000437,
"styleRecalcs": 12,
"styleRecalcDurationMs": 10.001000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 412.955,
"heapDeltaBytes": -5076812
},
{
"name": "canvas-idle",
"durationMs": 2016.401999999971,
"styleRecalcs": 12,
"styleRecalcDurationMs": 12.812000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 449.25000000000006,
"heapDeltaBytes": 1131804
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1873.4529999999836,
"styleRecalcs": 78,
"styleRecalcDurationMs": 42.251000000000005,
"layouts": 12,
"layoutDurationMs": 3.596,
"taskDurationMs": 821.4870000000001,
"heapDeltaBytes": -3458904
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1862.9130000000487,
"styleRecalcs": 76,
"styleRecalcDurationMs": 43.707,
"layouts": 12,
"layoutDurationMs": 4.023,
"taskDurationMs": 792.7080000000001,
"heapDeltaBytes": -4090636
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1956.9789999999898,
"styleRecalcs": 81,
"styleRecalcDurationMs": 53.507,
"layouts": 12,
"layoutDurationMs": 3.559,
"taskDurationMs": 942.1089999999999,
"heapDeltaBytes": -3731972
},
{
"name": "dom-widget-clipping",
"durationMs": 651.740000000018,
"styleRecalcs": 16,
"styleRecalcDurationMs": 14.5,
"layouts": 1,
"layoutDurationMs": 0.2979999999999999,
"taskDurationMs": 404.327,
"heapDeltaBytes": 13227388
},
{
"name": "dom-widget-clipping",
"durationMs": 601.3059999999655,
"styleRecalcs": 13,
"styleRecalcDurationMs": 9.819,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 376.20599999999996,
"heapDeltaBytes": 12678336
},
{
"name": "dom-widget-clipping",
"durationMs": 599.2739999999799,
"styleRecalcs": 14,
"styleRecalcDurationMs": 10.341,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 366.44800000000004,
"heapDeltaBytes": 11775652
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 599.3760000000066,
"styleRecalcs": 52,
"styleRecalcDurationMs": 18.133000000000003,
"layouts": 1,
"layoutDurationMs": 0.348,
"taskDurationMs": 400.229,
"heapDeltaBytes": 13532692
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 589.7679999999923,
"styleRecalcs": 48,
"styleRecalcDurationMs": 12.6,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 378.578,
"heapDeltaBytes": 13020400
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 583.0080000000066,
"styleRecalcs": 48,
"styleRecalcDurationMs": 12.763000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.911,
"heapDeltaBytes": 12975576
},
{
"name": "subgraph-idle",
"durationMs": 2013.7850000000412,
"styleRecalcs": 11,
"styleRecalcDurationMs": 11.315000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 409.866,
"heapDeltaBytes": 325596
},
{
"name": "subgraph-idle",
"durationMs": 2004.1009999999915,
"styleRecalcs": 13,
"styleRecalcDurationMs": 12.518999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 443.9189999999999,
"heapDeltaBytes": 974596
},
{
"name": "subgraph-idle",
"durationMs": 2010.3129999999965,
"styleRecalcs": 11,
"styleRecalcDurationMs": 11.075000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 446.13000000000005,
"heapDeltaBytes": 313492
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1990.6070000000113,
"styleRecalcs": 87,
"styleRecalcDurationMs": 51.95799999999999,
"layouts": 16,
"layoutDurationMs": 4.649,
"taskDurationMs": 934.503,
"heapDeltaBytes": -7108340
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1746.1140000000341,
"styleRecalcs": 76,
"styleRecalcDurationMs": 39.97,
"layouts": 16,
"layoutDurationMs": 4.896,
"taskDurationMs": 739.6669999999999,
"heapDeltaBytes": -7087552
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1730.8400000000006,
"styleRecalcs": 76,
"styleRecalcDurationMs": 40.519,
"layouts": 16,
"layoutDurationMs": 4.872999999999999,
"taskDurationMs": 717.878,
"heapDeltaBytes": -7696144
}
]
} |
|
lgtm |
a9ea00b to
59e5a64
Compare
What
Replace
canvas.offsetHeightwithcanvas.height / devicePixelRatioinrenderInfoto avoid forced synchronous layout.Why
renderInfois called ~2,631 times in a typical session. Each call readsthis.canvas.offsetHeight, which forces the browser to flush pending style/layout changes synchronously. With PrimeVue injecting styles dynamically and Vue patching the DOM, there are almost always pending mutations — converting every canvas-onlyrenderInfocall into a forced layout.How
canvas.heightis the DPR-scaled internal resolution (set inresizeCanvasascssHeight * devicePixelRatio). Dividing bydevicePixelRatioyields the same CSS pixel value asoffsetHeightwithout triggering layout.Verification
offsetHeightis not accessed when y is providedcanvas.height / devicePixelRatiopnpm typecheckpassespnpm lintpassesPerf Impact
Eliminates ~2,631 forced synchronous layouts per session from the canvas info panel.
┆Issue is synchronized with this Notion page by Unito