Files
IxMxAMAR 3f9e39bb44 v0.3.0: security hardening, audit fixes, 10 new ops, theme UX (matchMedia perf fix + sandbox + leaks + features)
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.
2026-05-17 15:41:19 +05:30

74 lines
2.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
}