Skip to content

Commit

Permalink
[Editor] Fix undoing an editor deletion (bug 1886959)
Browse files Browse the repository at this point in the history
The original bug was because the parent was null when trying to show
an highlight annotation which led to an exception.
That led me to think about having some null/non-null parent when removing
an editor: it's a mess especially if a destroyed parent is still attached
to an editor. Consequently, this patch always sets the parent to null when
deleting the editor.
  • Loading branch information
calixteman committed Mar 25, 2024
1 parent b0f54b2 commit 4339687
Show file tree
Hide file tree
Showing 10 changed files with 574 additions and 11 deletions.
5 changes: 2 additions & 3 deletions src/display/editor/annotation_editor_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,6 @@ class AnnotationEditorLayer {
* @param {AnnotationEditor} editor
*/
remove(editor) {
// Since we can undo a removal we need to keep the
// parent property as it is, so don't null it!

this.detach(editor);
this.#uiManager.removeEditor(editor);
editor.div.remove();
Expand Down Expand Up @@ -546,6 +543,7 @@ class AnnotationEditorLayer {
if (editor.needsToBeRebuilt()) {
editor.parent ||= this;
editor.rebuild();
editor.show();
} else {
this.add(editor);
}
Expand Down Expand Up @@ -842,6 +840,7 @@ class AnnotationEditorLayer {
setLayerDimensions(this.div, viewport);
for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
this.add(editor);
editor.rebuild();
}
// We're maybe rendering a layer which was invisible when we started to edit
// so we must set the different callbacks for it.
Expand Down
6 changes: 3 additions & 3 deletions src/display/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,6 @@ class AnnotationEditor {
rebuild() {
this.div?.addEventListener("focusin", this.#boundFocusin);
this.div?.addEventListener("focusout", this.#boundFocusout);
this.show(this._isVisible);
}

/**
Expand Down Expand Up @@ -1354,6 +1353,7 @@ class AnnotationEditor {
}
this.#telemetryTimeouts = null;
}
this.parent = null;
}

/**
Expand Down Expand Up @@ -1676,9 +1676,9 @@ class AnnotationEditor {

/**
* Show or hide this editor.
* @param {boolean} visible
* @param {boolean|undefined} visible
*/
show(visible) {
show(visible = this._isVisible) {
this.div.classList.toggle("hidden", !visible);
this._isVisible = visible;
}
Expand Down
11 changes: 9 additions & 2 deletions src/display/editor/highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,11 +418,11 @@ class HighlightEditor extends AnnotationEditor {

/** @inheritdoc */
remove() {
super.remove();
this.#cleanDrawLayer();
this._reportTelemetry({
action: "deleted",
});
super.remove();
}

/** @inheritdoc */
Expand Down Expand Up @@ -628,13 +628,19 @@ class HighlightEditor extends AnnotationEditor {
/** @inheritdoc */
select() {
super.select();
if (!this.#outlineId) {
return;
}
this.parent?.drawLayer.removeClass(this.#outlineId, "hovered");
this.parent?.drawLayer.addClass(this.#outlineId, "selected");
}

/** @inheritdoc */
unselect() {
super.unselect();
if (!this.#outlineId) {
return;
}
this.parent?.drawLayer.removeClass(this.#outlineId, "selected");
if (!this.#isFreeHighlight) {
this.#setCaret(/* start = */ false);
Expand All @@ -646,7 +652,8 @@ class HighlightEditor extends AnnotationEditor {
return !this.#isFreeHighlight;
}

show(visible) {
/** @inheritdoc */
show(visible = this._isVisible) {
super.show(visible);
if (this.parent) {
this.parent.drawLayer.show(this.#id, visible);
Expand Down
2 changes: 1 addition & 1 deletion src/display/editor/ink.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ class InkEditor extends AnnotationEditor {
this.allRawPaths.push(currentPath);
this.paths.push(bezier);
this.bezierPath2D.push(path2D);
this.rebuild();
this._uiManager.rebuild(this);
};

const undo = () => {
Expand Down
5 changes: 3 additions & 2 deletions src/display/editor/stamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class StampEditor extends AnnotationEditor {
return;
}

if (this.#bitmapId) {
if (this.#bitmapId && this.#canvas === null) {
this.#getBitmap();
}

Expand All @@ -241,7 +241,8 @@ class StampEditor extends AnnotationEditor {
this.#bitmapPromise ||
this.#bitmap ||
this.#bitmapUrl ||
this.#bitmapFile
this.#bitmapFile ||
this.#bitmapId
);
}

Expand Down
1 change: 1 addition & 0 deletions src/display/editor/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -1713,6 +1713,7 @@ class AnnotationEditorUIManager {
layer.addOrRebuild(editor);
} else {
this.addEditor(editor);
this.addToAnnotationStorage(editor);
}
}

Expand Down
115 changes: 115 additions & 0 deletions test/integration/freetext_editor_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3431,4 +3431,119 @@ describe("FreeText Editor", () => {
);
});
});

describe("Delete a freetext and undo it an other page", () => {
let pages;

beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});

afterAll(async () => {
await closePages(pages);
});

it("must check that a freetext can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToFreeText(page);

const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});

const data = "Hello PDF.js World !!";
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.waitForSelector(getEditorSelector(0), {
visible: true,
});
await page.type(`${getEditorSelector(0)} .internal`, data);

// Commit.
await page.keyboard.press("Escape");
await page.waitForSelector(
`${getEditorSelector(0)} .overlay.enabled`
);
await waitForSerialized(page, 1);

await page.waitForSelector(`${getEditorSelector(0)} button.delete`);
await page.click(`${getEditorSelector(0)} button.delete`);
await waitForSerialized(page, 0);

const twoToFourteen = Array.from(new Array(13).keys(), n => n + 2);
for (const pageNumber of twoToFourteen) {
const pageSelector = `.page[data-page-number = "${pageNumber}"]`;
await scrollIntoView(page, pageSelector);
}

await kbUndo(page);
await waitForSerialized(page, 1);

const thirteenToOne = Array.from(new Array(13).keys(), n => 13 - n);
for (const pageNumber of thirteenToOne) {
const pageSelector = `.page[data-page-number = "${pageNumber}"]`;
await scrollIntoView(page, pageSelector);
}

await page.waitForSelector(getEditorSelector(0));
})
);
});
});

describe("Delete a freetext, scroll and undo it", () => {
let pages;

beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});

afterAll(async () => {
await closePages(pages);
});

it("must check that a freetext can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToFreeText(page);

const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});

const data = "Hello PDF.js World !!";
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.waitForSelector(getEditorSelector(0), {
visible: true,
});
await page.type(`${getEditorSelector(0)} .internal`, data);

// Commit.
await page.keyboard.press("Escape");
await page.waitForSelector(
`${getEditorSelector(0)} .overlay.enabled`
);
await waitForSerialized(page, 1);

await page.waitForSelector(`${getEditorSelector(0)} button.delete`);
await page.click(`${getEditorSelector(0)} button.delete`);
await waitForSerialized(page, 0);

const twoToOne = Array.from(new Array(13).keys(), n => n + 2).concat(
Array.from(new Array(13).keys(), n => 13 - n)
);
for (const pageNumber of twoToOne) {
const pageSelector = `.page[data-page-number = "${pageNumber}"]`;
await scrollIntoView(page, pageSelector);
}

await kbUndo(page);
await waitForSerialized(page, 1);
await page.waitForSelector(getEditorSelector(0));
})
);
});
});
});
Loading

0 comments on commit 4339687

Please sign in to comment.