1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 14:48:08 +02:00

Merge pull request #17823 from calixteman/bug1886959

[Editor] Fix undoing an editor deletion (bug 1886959)
This commit is contained in:
calixteman 2024-03-25 14:42:32 +01:00 committed by GitHub
commit 3fbd6b5a77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 574 additions and 11 deletions

View file

@ -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();
@ -546,6 +543,7 @@ class AnnotationEditorLayer {
if (editor.needsToBeRebuilt()) {
editor.parent ||= this;
editor.rebuild();
editor.show();
} else {
this.add(editor);
}
@ -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.

View file

@ -1270,7 +1270,6 @@ class AnnotationEditor {
rebuild() {
this.div?.addEventListener("focusin", this.#boundFocusin);
this.div?.addEventListener("focusout", this.#boundFocusout);
this.show(this._isVisible);
}
/**
@ -1354,6 +1353,7 @@ class AnnotationEditor {
}
this.#telemetryTimeouts = null;
}
this.parent = null;
}
/**
@ -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;
}

View file

@ -418,11 +418,11 @@ class HighlightEditor extends AnnotationEditor {
/** @inheritdoc */
remove() {
super.remove();
this.#cleanDrawLayer();
this._reportTelemetry({
action: "deleted",
});
super.remove();
}
/** @inheritdoc */
@ -628,6 +628,9 @@ 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");
}
@ -635,6 +638,9 @@ class HighlightEditor extends AnnotationEditor {
/** @inheritdoc */
unselect() {
super.unselect();
if (!this.#outlineId) {
return;
}
this.parent?.drawLayer.removeClass(this.#outlineId, "selected");
if (!this.#isFreeHighlight) {
this.#setCaret(/* start = */ false);
@ -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);

View file

@ -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 = () => {

View file

@ -218,7 +218,7 @@ class StampEditor extends AnnotationEditor {
return;
}
if (this.#bitmapId) {
if (this.#bitmapId && this.#canvas === null) {
this.#getBitmap();
}
@ -241,7 +241,8 @@ class StampEditor extends AnnotationEditor {
this.#bitmapPromise ||
this.#bitmap ||
this.#bitmapUrl ||
this.#bitmapFile
this.#bitmapFile ||
this.#bitmapId
);
}

View file

@ -1713,6 +1713,7 @@ class AnnotationEditorUIManager {
layer.addOrRebuild(editor);
} else {
this.addEditor(editor);
this.addToAnnotationStorage(editor);
}
}

View file

@ -3431,4 +3431,119 @@ describe("FreeText Editor", () => {
);
});
});
describe("Delete a freetext and undo it on another 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));
})
);
});
});
});

View file

@ -25,6 +25,7 @@ import {
kbFocusNext,
kbFocusPrevious,
kbSelectAll,
kbUndo,
loadAndWait,
scrollIntoView,
waitForSerialized,
@ -1647,4 +1648,157 @@ describe("Highlight Editor", () => {
);
});
});
describe("Undo a highlight", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a highlight can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorHighlight");
await page.waitForSelector(".annotationEditorLayer.highlightEditing");
const rect = await getSpanRectFromText(page, 1, "Abstract");
const x = rect.x + rect.width / 2;
const y = rect.y + rect.height / 2;
await page.mouse.click(x, y, { count: 2, delay: 100 });
await page.waitForSelector(getEditorSelector(0));
await waitForSerialized(page, 1);
await page.waitForSelector(`${getEditorSelector(0)} button.delete`);
await page.click(`${getEditorSelector(0)} button.delete`);
await waitForSerialized(page, 0);
await kbUndo(page);
await waitForSerialized(page, 1);
await page.waitForSelector(getEditorSelector(0));
})
);
});
});
describe("Delete a highlight and undo it an other page", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait(
"tracemonkey.pdf",
".annotationEditorLayer",
null,
null,
{
highlightEditorColors:
"yellow=#FFFF00,green=#00FF00,blue=#0000FF,pink=#FF00FF,red=#FF0000",
}
);
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a highlight can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorHighlight");
await page.waitForSelector(".annotationEditorLayer.highlightEditing");
const rect = await getSpanRectFromText(page, 1, "Abstract");
const x = rect.x + rect.width / 2;
const y = rect.y + rect.height / 2;
await page.mouse.click(x, y, { count: 2, delay: 100 });
await page.waitForSelector(getEditorSelector(0));
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));
await page.waitForSelector(
`.page[data-page-number = "1"] svg.highlight[fill = "#FFFF00"]`
);
})
);
});
});
describe("Delete a highlight, scroll and undo it", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait(
"tracemonkey.pdf",
".annotationEditorLayer",
null,
null,
{
highlightEditorColors:
"yellow=#FFFF00,green=#00FF00,blue=#0000FF,pink=#FF00FF,red=#FF0000",
}
);
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a highlight can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorHighlight");
await page.waitForSelector(".annotationEditorLayer.highlightEditing");
const rect = await getSpanRectFromText(page, 1, "Abstract");
const x = rect.x + rect.width / 2;
const y = rect.y + rect.height / 2;
await page.mouse.click(x, y, { count: 2, delay: 100 });
await page.waitForSelector(getEditorSelector(0));
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));
await page.waitForSelector(
`.page[data-page-number = "1"] svg.highlight[fill = "#FFFF00"]`
);
})
);
});
});
});

View file

@ -24,6 +24,7 @@ import {
kbUndo,
loadAndWait,
scrollIntoView,
waitForSerialized,
waitForStorageEntries,
} from "./test_utils.mjs";
@ -296,4 +297,166 @@ describe("Ink Editor", () => {
);
});
});
describe("Undo a draw", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a draw can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorInk");
await page.waitForSelector(".annotationEditorLayer.inkEditing");
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const xStart = rect.x + 300;
const yStart = rect.y + 300;
const clickHandle = await waitForPointerUp(page);
await page.mouse.move(xStart, yStart);
await page.mouse.down();
await page.mouse.move(xStart + 50, yStart + 50);
await page.mouse.up();
await awaitPromise(clickHandle);
await commit(page);
await page.waitForSelector(getEditorSelector(0));
await waitForSerialized(page, 1);
await page.waitForSelector(`${getEditorSelector(0)} button.delete`);
await page.click(`${getEditorSelector(0)} button.delete`);
await waitForSerialized(page, 0);
await kbUndo(page);
await waitForSerialized(page, 1);
await page.waitForSelector(getEditorSelector(0));
})
);
});
});
describe("Delete a draw and undo it on another page", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a draw can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorInk");
await page.waitForSelector(".annotationEditorLayer.inkEditing");
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const xStart = rect.x + 300;
const yStart = rect.y + 300;
const clickHandle = await waitForPointerUp(page);
await page.mouse.move(xStart, yStart);
await page.mouse.down();
await page.mouse.move(xStart + 50, yStart + 50);
await page.mouse.up();
await awaitPromise(clickHandle);
await commit(page);
await page.waitForSelector(getEditorSelector(0));
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 draw, scroll and undo it", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a draw can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorInk");
await page.waitForSelector(".annotationEditorLayer.inkEditing");
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const xStart = rect.x + 300;
const yStart = rect.y + 300;
const clickHandle = await waitForPointerUp(page);
await page.mouse.move(xStart, yStart);
await page.mouse.down();
await page.mouse.move(xStart + 50, yStart + 50);
await page.mouse.up();
await awaitPromise(clickHandle);
await commit(page);
await page.waitForSelector(getEditorSelector(0));
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));
})
);
});
});
});

View file

@ -24,10 +24,13 @@ import {
kbCopy,
kbPaste,
kbSelectAll,
kbUndo,
loadAndWait,
scrollIntoView,
serializeBitmapDimensions,
waitForAnnotationEditorLayer,
waitForSelectedEditor,
waitForSerialized,
waitForStorageEntries,
} from "./test_utils.mjs";
import { fileURLToPath } from "url";
@ -605,4 +608,124 @@ describe("Stamp Editor", () => {
}
});
});
describe("Undo a stamp", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a stamp can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorStamp");
await page.waitForSelector(".annotationEditorLayer.stampEditing");
await copyImage(page, "../images/firefox_logo.png", 0);
await page.waitForSelector(getEditorSelector(0));
await waitForSerialized(page, 1);
await page.waitForSelector(`${getEditorSelector(0)} button.delete`);
await page.click(`${getEditorSelector(0)} button.delete`);
await waitForSerialized(page, 0);
await kbUndo(page);
await waitForSerialized(page, 1);
await page.waitForSelector(getEditorSelector(0));
})
);
});
});
describe("Delete a stamp and undo it on another page", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a stamp can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorStamp");
await page.waitForSelector(".annotationEditorLayer.stampEditing");
await copyImage(page, "../images/firefox_logo.png", 0);
await page.waitForSelector(getEditorSelector(0));
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 stamp, scroll and undo it", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that a stamp can be undone", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorStamp");
await page.waitForSelector(".annotationEditorLayer.stampEditing");
await copyImage(page, "../images/firefox_logo.png", 0);
await page.waitForSelector(getEditorSelector(0));
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));
})
);
});
});
});