Skip to content

Commit a5536ae

Browse files
tinchox5claude
andcommitted
perf(clone): skip idle machinery per child in fast mode, make canvas pre-rAF Safari-only
- idleCallback fast path: direct Promise.all over deepClone instead of deal()/idle() wrappers (removes an extra promise + two closures per node on large trees). - CANVAS snapshot: the unconditional requestAnimationFrame cost a serialized frame per canvas; only WebKit needs it to materialize the poked buffer. Other engines rely on toDataURL being synchronous, with the existing blank-result retry as safety net. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 7a5179d commit a5536ae

2 files changed

Lines changed: 12 additions & 2 deletions

File tree

src/core/clone.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
getUnscaledDimensions,
2222
createCheckboxRadioReplacement
2323
} from '../utils/clone.helpers.js'
24-
import { isFirefox } from '../utils/browser.js'
24+
import { isFirefox, isSafari } from '../utils/browser.js'
2525

2626
// helper implementations moved to ../utils/clone.helpers.js
2727

@@ -164,7 +164,11 @@ export async function deepClone(node, sessionCache, options) {
164164
try {
165165
const ctx = node.getContext('2d', { willReadFrequently: true })
166166
try { ctx && ctx.getImageData(0, 0, 1, 1) } catch { }
167-
await new Promise(r => requestAnimationFrame(r)) // deja materializar el frame
167+
// WebKit needs a frame for the poke to materialize the buffer; on other engines
168+
// toDataURL is synchronous with issued commands, so an unconditional rAF cost a
169+
// serialized frame (≥16ms) per canvas — dashboards with N charts paid N frames.
170+
// The blank-result retry below still covers any engine that returns an empty frame.
171+
if (isSafari()) await new Promise(r => requestAnimationFrame(r))
168172

169173
url = node.toDataURL('image/png')
170174

src/utils/clone.helpers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ import { inlineAllStyles } from '../modules/styles.js'
1717
* @returns {Promise<(Node|null)[]>}
1818
*/
1919
export function idleCallback(childList, callback, fast) {
20+
if (fast) {
21+
// Fast mode ran every child through deal()/idle() anyway (synchronously), paying an extra
22+
// promise + two closures per node — tens of thousands of allocations on large trees.
23+
// Call straight through; Promise.all keeps the same concurrency and ordering.
24+
return Promise.all(childList.map((child) => new Promise((resolve) => callback(child, resolve))))
25+
}
2026
return Promise.all(childList.map((child) => {
2127
return new Promise((resolve) => {
2228
function deal() {

0 commit comments

Comments
 (0)