diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 1ca8f78..72ce052 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -1769,7 +1769,7 @@ test('rgthree Power Lora Loader updates strength without reloading workflow anal ]); }); -test('rgthree Power Lora Loader detects removals made through its context menu', async () => { +test('rgthree Power Lora Loader detects removals when canvas is dirtied', async () => { const configureCustomNodeModelAdapter = eval( `(${extractMethod(modelResolverSource, 'configureCustomNodeModelAdapter')})` ); @@ -1807,6 +1807,7 @@ test('rgthree Power Lora Loader detects removals made through its context menu', configureCustomNodeModelAdapter.call(resolver, node); node.widgets.splice(1, 1); + node.setDirtyCanvas(true, true); await Promise.resolve(); assert.deepEqual(calls, [ @@ -1815,6 +1816,57 @@ test('rgthree Power Lora Loader detects removals made through its context menu', ]); }); +test('rgthree Power Lora Loader does not monkey-patch widgets.splice or cause recursion on reactive proxies', () => { + const configureCustomNodeModelAdapter = eval( + `(${extractMethod(modelResolverSource, 'configureCustomNodeModelAdapter')})` + ); + const getCustomNodeModelListSignature = eval( + `(${extractMethod(modelResolverSource, 'getCustomNodeModelListSignature')})` + ); + const getCustomNodeModelStrengthSignature = eval( + `(${extractMethod(modelResolverSource, 'getCustomNodeModelStrengthSignature')})` + ); + const rawWidgets = [ + { name: 'lora_1', value: { on: true, lora: 'first.safetensors', strength: 1 } }, + ]; + const reactiveWidgets = new Proxy(rawWidgets, { + get(target, prop, receiver) { + if (prop === 'splice') { + return function(...args) { + return target[prop].apply(this, args); + }; + } + return Reflect.get(target, prop, receiver); + }, + set(target, prop, value) { + target[prop] = value; + return true; + }, + }); + + const node = { + comfyClass: 'Power Lora Loader (rgthree)', + widgets: reactiveWidgets, + setDirtyCanvas() {}, + }; + const resolver = { + customNodeModelAdapterStates: new WeakMap(), + getCustomNodeModelListSignature, + getCustomNodeModelStrengthSignature, + scheduleNodeContextMenuAnalysis: () => {}, + dialog: { + isWorkflowRefreshSuppressed: () => false, + scheduleActiveWorkflowRefresh: () => {}, + }, + }; + + configureCustomNodeModelAdapter.call(resolver, node); + assert.strictEqual(node.widgets.splice.__modelResolverCustomNodeAdapter, undefined); + assert.doesNotThrow(() => { + node.widgets.splice(0, 1); + }); +}); + test('rgthree dynamic lora widgets bypass the generic full workflow refresh', () => { const configureNodeContextMenu = eval( `(${extractMethod(modelResolverSource, 'configureNodeContextMenu')})` diff --git a/web/resolver/custom_nodes/rgthree_power_lora_loader.js b/web/resolver/custom_nodes/rgthree_power_lora_loader.js index c3c3f6e..ca1d258 100644 --- a/web/resolver/custom_nodes/rgthree_power_lora_loader.js +++ b/web/resolver/custom_nodes/rgthree_power_lora_loader.js @@ -32,31 +32,17 @@ function observePowerLoraLoader(node, notify) { typeof node.setDirtyCanvas !== 'function' || node.setDirtyCanvas.__modelResolverCustomNodeAdapter ) { - if (!Array.isArray(node.widgets)) return; - } else { - const originalSetDirtyCanvas = node.setDirtyCanvas; - const wrappedSetDirtyCanvas = function() { - const result = originalSetDirtyCanvas.apply(this, arguments); - queueInspection(); - return result; - }; - wrappedSetDirtyCanvas.__modelResolverCustomNodeAdapter = true; - node.setDirtyCanvas = wrappedSetDirtyCanvas; + return; } - if ( - typeof node.widgets.splice === 'function' - && !node.widgets.splice.__modelResolverCustomNodeAdapter - ) { - const originalSplice = node.widgets.splice; - const wrappedSplice = function() { - const result = originalSplice.apply(this, arguments); - queueInspection(); - return result; - }; - wrappedSplice.__modelResolverCustomNodeAdapter = true; - node.widgets.splice = wrappedSplice; - } + const originalSetDirtyCanvas = node.setDirtyCanvas; + const wrappedSetDirtyCanvas = function() { + const result = originalSetDirtyCanvas.apply(this, arguments); + queueInspection(); + return result; + }; + wrappedSetDirtyCanvas.__modelResolverCustomNodeAdapter = true; + node.setDirtyCanvas = wrappedSetDirtyCanvas; } export const rgthreePowerLoraLoaderAdapter = Object.freeze({