fix: prevent infinite recursion in rgthree Power Lora Loader adapter (#7)
- Remove node.widgets.splice monkey-patching in rgthree Power Lora Loader adapter to avoid infinite recursion with Vue 3 reactive array proxies on modern ComfyUI frontend. - Rely on setDirtyCanvas to trigger inspection updates. - Update test suite to verify no widgets.splice wrapping occurs and reactive proxies do not recurse.
This commit is contained in:
@@ -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')})`
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user