Skip to content

Commit 75ceaa4

Browse files
committed
[FIX] SheetView: dirtify sheet viewport at UPDATE_CELL
the issue was found through an autofill bug. To reproduce: - Add a data validation checkbox to A1:A2 - Add a format (like `%`) to A1:A2 - autofill A1 into A2 -> crash What happens? For starters, the plugin `SheetViewPlugin` is always listening to selection events and will update the viewports 'scroll' value accordingly. During the autofill handling of the command, a bunch of `UPDATE_CELL` commands are dispatched, which invalidates the whole state of `HeaderPositionsUIPlugin`. After that dispatch, the plugin also updates the selection, which triggers `SheetViewPlugin` to update its viewports but this manipulation relies on the state of `HeaderPositionsUIPlugin`, which was just invalidated! It was already known that during a command dispatch, the general state of plugins might be unstable and `SheetViewPlugin` accounted for it by *tagging* sheets that might have an unstable state and it would skip the viewport `scroll` update after selection events. However, we limited the *tagging* for specific payloads of `UPDATE_CELL` and the condition set on the style was incorrect. Indeed, a style object without the `fontSize` does not mean that we do not update the fontsize but rather that we reset it to its default value which has a direct impact on the header size and position (hence the invalidation of state in `HeaderPositionsUIPlugin`. All-in-all, The presence of the `style` key in the `UPDATE_CELL` command payload is enough to tag a sheet as invalid. A side note: While changing the content or format of a cell can have cross-sheet impact due to the evaluation process (a cell will take the format of its reference without a format on its own), it is not the case for the style. As such, we could make separate cases for commands that update the content/format, which might theoretically impact every sheet, and commands that only impact the style, which only impacts the targetted sheet. Such improvement can target the master dev branch. closes #8189 Task: 5953775 X-original-commit: a962b50 Signed-off-by: Pierre Rousseau (pro) <pro@odoo.com> Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
1 parent 67fec55 commit 75ceaa4

File tree

4 files changed

+28
-6
lines changed

4 files changed

+28
-6
lines changed

packages/o-spreadsheet-engine/src/plugins/ui_stateful/header_positions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export class HeaderPositionsUIPlugin extends UIPlugin {
2121
this.headerPositions[sheetId] = this.computeHeaderPositionsOfSheet(sheetId);
2222
}
2323
break;
24+
// Either the content, format or style can impact the header sizes of a sheet
25+
// As such, every command can have a potential effect on the viewport
2426
case "UPDATE_CELL":
2527
this.headerPositions = {};
2628
this.isDirty = true;

packages/o-spreadsheet-engine/src/plugins/ui_stateful/sheetview.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,12 @@ export class SheetViewPlugin extends UIPlugin {
258258
case "FOLD_ALL_HEADER_GROUPS":
259259
this.sheetsWithDirtyViewports.add(cmd.sheetId);
260260
break;
261+
// Either the content, format or style can impact the header sizes of a sheet
262+
// As such, every command can have a potential effect on the viewport
261263
case "UPDATE_CELL":
262-
// update cell content or format can change hidden rows because of data filters
263-
if ("content" in cmd || "format" in cmd || cmd.style?.fontSize !== undefined) {
264-
for (const sheetId of this.getters.getSheetIds()) {
265-
this.sheetsWithDirtyViewports.add(sheetId);
266-
}
264+
case "SET_FORMATTING":
265+
for (const sheetId of this.getters.getSheetIds()) {
266+
this.sheetsWithDirtyViewports.add(sheetId);
267267
}
268268
break;
269269
case "DELETE_SHEET":

tests/autofill/autofill_plugin.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
selectCell,
1818
setBorders,
1919
setCellContent,
20+
setFormat,
2021
setSelection,
2122
updateCell,
2223
} from "../test_helpers/commands_helpers";
@@ -900,4 +901,12 @@ describe("Autofill", () => {
900901
setCellContent(model, "A1", "[label](url)");
901902
expect(autofillTooltip("A1", "A2")).toBe("label");
902903
});
904+
905+
test("Autofill that triggers no change does not crash", () => {
906+
addDataValidation(model, "A1:A2", "1", { type: "isBoolean", values: [] });
907+
setCellContent(model, "A1", "FALSE");
908+
setCellContent(model, "A2", "FALSE");
909+
setFormat(model, "A1:A2", "0.00%");
910+
autofill(model, "A1", "A2"); // A1 and A2 are exactly the same
911+
});
903912
});

tests/sheet/sheetview_plugin.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,18 @@ describe("Viewport of Simple sheet", () => {
888888
right: 10,
889889
top: 0,
890890
});
891-
setFormatting(model, "A1:A20", { fontSize: 8 });
891+
setFormatting(model, "A1:A20", { fontSize: 24 });
892+
expect(model.getters.getActiveMainViewport()).toEqual({
893+
bottom: 30,
894+
left: 0,
895+
right: 10,
896+
top: 0,
897+
});
898+
899+
const sheetId = model.getters.getActiveSheetId();
900+
for (let i = 0; i < 20; ++i) {
901+
model.dispatch("UPDATE_CELL", { sheetId, col: 0, row: i, style: {} });
902+
}
892903
expect(model.getters.getActiveMainViewport()).toEqual({
893904
bottom: 43,
894905
left: 0,

0 commit comments

Comments
 (0)