Critical security fixes ----------------------- - Sandbox Jinja2 in template_render + prompt_template_render (was RCE via shared workflows) - Drop user home dir from filesystem allow-list (was arbitrary delete/overwrite) - Confine save_image_with_manifest + sanitize filename basename - SSRF block on http_get/http_post/notify_webhook (catches 169.254.169.254 metadata) - Case-insensitive path confinement on Windows Critical correctness/perf ------------------------- - halftone_dots: O(N^2) -> O(cells*36); was freezing worker on 1024+ images - Preserve latent dict keys (noise_mask, batch_index) in latent_math/noise/upscale - Preserve alpha channel in invert + threshold_binary on RGBA inputs - Force odd kernel in mask_erode/mask_dilate to prevent H+1 shape mismatch - Snap aspect_ratio_pick output to multiples of 8 (was crashing SD UNet) Theme engine (JS) ----------------- - Hoist window.matchMedia() out of per-frame hot path -> recovers 200 FPS from 25 - Wrap-once draw method to stop stomping other extensions wrappers - nodeRemoved hook + WeakMap groups + in-place canvas reuse -> three memory leaks closed - Wire thickness now uses connections_width (the wrap was a no-op vs LiteGraph) - onGroupAdded auto-themes new groups; budget change triggers redraw - Gradient caching in synthwave/glassmorphic New ops (10) ------------ - json_path (programming): dotted/bracketed path extract - latent_nan_guard, latent_pad_crop (mask_latent) - mask_from_depth, mask_invert (mask_latent) - color_match_histogram, image_composite_over (image_pro/value) - frequency_separation (image_pro/style) - pad_to_multiple (image_pro/spatial) - save_images_zip (io_workflow) - llm_enhance_prompt via Ollama / LM Studio / llama.cpp (prompt) Theme UX -------- - Right-click "Theme..." submenu on every node (including vanilla ComfyUI nodes) - Auto-contrast title text (WCAG AA-based flip) Tests: 373 Python + 33 JS = 406 passing (added 33 new tests for security + features). Full review notes in CHANGELOG.md.
74 lines
2.6 KiB
JavaScript
74 lines
2.6 KiB
JavaScript
/**
|
||
* OffscreenCanvas cache for expensive static draws.
|
||
*
|
||
* Use case: a theme draws the same complex pattern (scanlines, hatch, grid,
|
||
* dither) inside every node's onDrawForeground every frame. Each individual
|
||
* draw is fast, but at 60 FPS × 20 nodes × N strokes, the cost compounds.
|
||
* Render once to an OffscreenCanvas keyed by (theme-id, width, height), then
|
||
* blit with a single drawImage call on subsequent frames.
|
||
*
|
||
* Cache is attached to the node (`node._mp_pattern_cache`) so it dies with
|
||
* the node and doesn't leak across graph reloads.
|
||
*/
|
||
|
||
const HAS_OFFSCREEN = typeof OffscreenCanvas !== "undefined";
|
||
|
||
|
||
/**
|
||
* Get-or-render a cached pattern for this node.
|
||
*
|
||
* Cache is keyed by (node, themeId) — only ONE canvas per (node, themeId)
|
||
* exists at a time. On size change we resize the existing canvas in place and
|
||
* re-render rather than allocating a fresh one, which used to thrash the GC
|
||
* (and re-run e_ink's per-pixel loop) on every mouse-move during a node
|
||
* resize.
|
||
*
|
||
* @param {Object} node The LiteGraph node.
|
||
* @param {String} themeId Used as the cache key namespace.
|
||
* @param {Number} width Pattern width (typically node.size[0]).
|
||
* @param {Number} height Pattern height (typically node.size[1]).
|
||
* @param {Function} render Callback `(ctx, width, height) => void` that draws
|
||
* the pattern onto the provided context.
|
||
* @returns The OffscreenCanvas (or HTMLCanvasElement fallback) ready to blit.
|
||
* Pass it to your main ctx via `ctx.drawImage(canvas, 0, 0)`.
|
||
*/
|
||
export function cachedPattern(node, themeId, width, height, render) {
|
||
if (width <= 0 || height <= 0) return null;
|
||
const w = Math.ceil(width);
|
||
const h = Math.ceil(height);
|
||
if (!node._mp_pattern_cache) node._mp_pattern_cache = {};
|
||
let entry = node._mp_pattern_cache[themeId];
|
||
|
||
if (entry && entry.w === w && entry.h === h) {
|
||
return entry.canvas;
|
||
}
|
||
|
||
let canvas;
|
||
if (entry) {
|
||
// Reuse the existing canvas — resizing is faster than allocating.
|
||
canvas = entry.canvas;
|
||
canvas.width = w;
|
||
canvas.height = h;
|
||
} else {
|
||
canvas = HAS_OFFSCREEN
|
||
? new OffscreenCanvas(w, h)
|
||
: Object.assign(document.createElement("canvas"), { width: w, height: h });
|
||
}
|
||
|
||
const ctx = canvas.getContext("2d");
|
||
// Setting width/height clears the canvas implicitly; just render.
|
||
render(ctx, w, h);
|
||
|
||
node._mp_pattern_cache[themeId] = { canvas, w, h };
|
||
return canvas;
|
||
}
|
||
|
||
|
||
/**
|
||
* Invalidate a node's pattern cache. Call when a theme changes or when the
|
||
* node is going to be re-themed.
|
||
*/
|
||
export function invalidateCache(node) {
|
||
if (node) delete node._mp_pattern_cache;
|
||
}
|