[FIX] component: make concurrent renderings more robust

Here is a situation that can happen in some complicated case:

1. a parent component is rendered, which includes some children
2. it is then willPatched
3. the sub components are then mounted/willUnmounted
4. because of complicated business logic, this causes the parent
component to be rerendered (before parent "patched" method is called)
5. owl will internally reset its currentfiber to null (but there is a
pending rendering!)
6. subsequent rendering will ignore pending rendering
7. havoc ensues

This is actually one of the reason why modifying a component state in a
willPatch component is actually not a good idea.  However, the good news
is that this specific situation can be properly handled: we can simply
make sure that we do not reset currentFiber to null if there is a new
pending rendering.

closes #728
This commit is contained in:
Géry Debongnie
2020-09-14 22:20:37 +02:00
committed by aab-odoo
parent 8d25bddda4
commit 2529aa3ef2
3 changed files with 36 additions and 4 deletions
+3 -3
View File
@@ -496,9 +496,9 @@ export class Component<Props extends {} = any, T extends Env = Env> {
}
this.willUnmount();
__owl__.isMounted = false;
if (this.__owl__.currentFiber) {
this.__owl__.currentFiber.isCompleted = true;
this.__owl__.currentFiber.root.counter = 0;
if (__owl__.currentFiber) {
__owl__.currentFiber.isCompleted = true;
__owl__.currentFiber.root.counter = 0;
}
const children = __owl__.children;
for (let id in children) {
+3 -1
View File
@@ -249,7 +249,9 @@ export class Fiber {
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
}
}
component.__owl__.currentFiber = null;
if (fiber === component.__owl__.currentFiber) {
component.__owl__.currentFiber = null;
}
}
// insert into the DOM (mount case)
+30
View File
@@ -3064,6 +3064,36 @@ describe("random stuff/miscellaneous", () => {
await nextTick();
expect(fixture.textContent!.trim()).toBe("2__3");
});
test("two renderings initiated between willPatch and patched", async () => {
let app;
class Panel extends Component {
static template = xml`<abc><t t-esc="props.val"/></abc>`;
mounted() {
app.render();
}
willUnmount() {
app.render();
}
}
// Main root component
class App extends Component {
static components = { Panel };
static template = xml`<div><Panel t-key="'panel_' + state.panel" val="state.panel"/></div>`;
state = useState({ panel: "Panel1" });
}
app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><abc>Panel1</abc></div>");
app.state.panel = "Panel2";
await nextTick();
expect(fixture.innerHTML).toBe("<div><abc>Panel2</abc></div>");
});
});
describe("widget and observable state", () => {