mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 07:30:11 +00:00
fix(litegraph): Resolve workflow corruption from subgraph serialization
This commit fixes a critical issue where saving a workflow with complex subgraphs could lead to a corrupted file, causing errors upon loading. The root cause was twofold: 1. **Unsafe Object Cloning:** The `LiteGraph.cloneObject` function used `JSON.stringify`, which cannot handle circular references that can exist in a nodes properties, especially in complex graphs with promoted widgets. This has been replaced with `_.cloneDeep` from lodash for robust and safe deep cloning. 2. **Unsafe Node Configuration:** The `LGraphNode.configure` method used a generic loop that would overwrite the `inputs` and `outputs` arrays with plain objects from the serialized data. This created a temporary but dangerous state where class instances were replaced by plain objects, leading to `TypeError` exceptions when methods or private fields were accessed on them. The `configure` method has been refactored to handle `inputs`, `outputs`, and `properties` explicitly and safely, ensuring they are always proper class instances. This fix makes the node loading process more robust and predictable, preventing data corruption and ensuring the stability of workflows with subgraphs.
This commit is contained in:
@@ -769,12 +769,7 @@ export class LGraphNode
|
||||
this.graph._version++
|
||||
}
|
||||
for (const j in info) {
|
||||
if (j == 'properties') {
|
||||
// i don't want to clone properties, I want to reuse the old container
|
||||
for (const k in info.properties) {
|
||||
this.properties[k] = info.properties[k]
|
||||
this.onPropertyChanged?.(k, info.properties[k])
|
||||
}
|
||||
if (j === 'properties' || j === 'inputs' || j === 'outputs') {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -798,14 +793,28 @@ export class LGraphNode
|
||||
}
|
||||
}
|
||||
|
||||
if (info.properties) {
|
||||
for (const k in info.properties) {
|
||||
this.properties[k] = info.properties[k]
|
||||
this.onPropertyChanged?.(k, info.properties[k])
|
||||
}
|
||||
}
|
||||
|
||||
if (!info.title) {
|
||||
this.title = this.constructor.title
|
||||
}
|
||||
|
||||
this.inputs ??= []
|
||||
this.inputs = this.inputs.map((input) =>
|
||||
toClass(NodeInputSlot, input, this)
|
||||
)
|
||||
if (info.inputs) {
|
||||
this.inputs = info.inputs.map((input) =>
|
||||
toClass(NodeInputSlot, input, this)
|
||||
)
|
||||
} else {
|
||||
this.inputs ??= []
|
||||
this.inputs = this.inputs.map((input) =>
|
||||
toClass(NodeInputSlot, input, this)
|
||||
)
|
||||
}
|
||||
|
||||
for (const [i, input] of this.inputs.entries()) {
|
||||
const link =
|
||||
this.graph && input.link != null
|
||||
@@ -815,10 +824,17 @@ export class LGraphNode
|
||||
this.onInputAdded?.(input)
|
||||
}
|
||||
|
||||
this.outputs ??= []
|
||||
this.outputs = this.outputs.map((output) =>
|
||||
toClass(NodeOutputSlot, output, this)
|
||||
)
|
||||
if (info.outputs) {
|
||||
this.outputs = info.outputs.map((output) =>
|
||||
toClass(NodeOutputSlot, output, this)
|
||||
)
|
||||
} else {
|
||||
this.outputs ??= []
|
||||
this.outputs = this.outputs.map((output) =>
|
||||
toClass(NodeOutputSlot, output, this)
|
||||
)
|
||||
}
|
||||
|
||||
for (const [i, output] of this.outputs.entries()) {
|
||||
if (!output.links) continue
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import _ from 'lodash'
|
||||
|
||||
import { ContextMenu } from './ContextMenu'
|
||||
import { CurveEditor } from './CurveEditor'
|
||||
import { DragAndScale } from './DragAndScale'
|
||||
@@ -641,7 +643,7 @@ export class LiteGraphGlobal {
|
||||
): WhenNullish<T, null> {
|
||||
if (obj == null) return null as WhenNullish<T, null>
|
||||
|
||||
const r = JSON.parse(JSON.stringify(obj))
|
||||
const r = _.cloneDeep(obj)
|
||||
if (!target) return r
|
||||
|
||||
for (const i in r) {
|
||||
|
||||
Reference in New Issue
Block a user