[Bug]: Command manager is crashing on certain undo operations
alexandernst opened this issue · comments
What happened?
I found a certain case in which the command manager will crash while trying to undo a batch operation.
Consider the following steps:
node1 = new shape()
node1.addTo(graph)
node2 = new shape()
node2.addTo(graph)
group = new shape()
group.addTo(graph)
group.embed([node1, node2])
Now consider we use the Selection
and the Clipboard
plugins to execute the following steps:
selection.collection.reset([group])
clipboard.copyElements(selection.collection, graph)
clipboard.pasteCells(graph)
This will add the following commands to the undo stack of the command manager:
1. add (node1)
2. add (node2)
3. add (group)
At this point, running commandManager.undo()
will trigger a crash. This is so because (I think) the command manager will start undoing the operations as so:
1. remove (group)
2. remove (node1)
3. remove (node2)
The problem is that the moment the group
element is removed (step 1), node1
and node2
elements will get removed as well (as they are embedded in the group
element). When step 2 is executed (remove node1), node1
won't exist, which will lead to the crash.
Demo: https://codesandbox.io/s/rappid-selection-copy-paste-bug-kin1pg
Note that moving lines [99-101] somewhere before creating the nodes will fix the issue (I believe because the undo stack will change the order of the commands).
Code
import "./style.scss";
import "./rappid.css";
import * as joint from "./rappid";
class MyGroup extends joint.dia.Element {
defaults() {
return {
...super.defaults,
type: "custom.MyGroup",
size: { width: 100, height: 250 },
attrs: {
body: {
refWidth: "100%",
refHeight: "100%",
strokeWidth: 2,
stroke: "#000000",
fill: "#FFFFFF"
},
label: {
textVerticalAnchor: "middle",
textAnchor: "middle",
refX: "50%",
refY: "50%",
fontSize: 14,
fill: "#333333"
}
}
};
}
markup = [
{
tagName: "rect",
selector: "body"
},
{
tagName: "text",
selector: "label"
}
];
}
const NS = Object.assign(joint.shapes, {
custom: { MyGroup }
});
const $node = document.getElementById("canvas");
const graph = new joint.dia.Graph(
{},
{
cellNamespace: NS
}
);
const paper = new joint.dia.Paper({
model: graph,
width: 500,
height: 500,
cellViewNamespace: NS
});
const paperScroller = new joint.ui.PaperScroller({
paper: paper
});
$node.replaceChildren(paperScroller.render().el);
const clipboard = new joint.ui.Clipboard({
useLocalStorage: true,
deep: true,
translate: {
dx: 120,
dy: 20
}
});
const selection = new joint.ui.Selection({
paper: paper,
strictSelection: true
});
const commandManager = new joint.dia.CommandManager({
graph: graph,
stackLimit: 50
});
var c1 = new joint.shapes.standard.Rectangle({
size: { width: 50, height: 50 }
});
c1.position(75, 150);
c1.addTo(graph);
var c2 = new joint.shapes.standard.Rectangle({
size: { width: 50, height: 50 }
});
c2.position(75, 250);
c2.addTo(graph);
const p = new MyGroup();
p.position(50, 100);
p.addTo(graph);
p.embed([c1, c2]);
// Trigger bug
selection.collection.reset([p]);
clipboard.copyElements(selection.collection, graph);
console.log(structuredClone(commandManager.undoStack));
clipboard.pasteCells(graph);
console.log(structuredClone(commandManager.undoStack));
commandManager.undo();
Version
3.7.4
What browsers are you seeing the problem on?
Chrome
What operating system are you seeing the problem on?
Mac
Thanks @alexandernst for the report and the reproducible example. We'll release a patch soon.
@zbynekstara Is this going to be release in JS+ 3.7.2? If "yes", when can I expect it?