mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-01 03:47:20 +02:00
Fix the selection rendering when a page has been destroyed and rendered again (bug 2054348)
The draw layer keeps a reference on the text layer div in order to render the selection, but it was destroyed only along with the annotation editor layer. Hence, when the editor is disabled, like in Firefox for Android, scrolling far enough to destroy a page view left the draw layer with a reference on a removed text layer: the new one was never registered, consequently no selection was rendered anymore on that page.
This commit is contained in:
parent
73707b62e9
commit
9e02ceed09
@ -641,7 +641,8 @@ class DrawLayer {
|
||||
textLayerData.selectionDiv = div;
|
||||
}
|
||||
|
||||
if (!div.parentNode && drawLayer.#parent) {
|
||||
if (drawLayer.#parent && div.parentNode !== drawLayer.#parent) {
|
||||
// The div can still be in a canvas wrapper which has been removed.
|
||||
drawLayer.#parent.append(div);
|
||||
this.#selections.add(div);
|
||||
}
|
||||
|
||||
@ -20,9 +20,11 @@
|
||||
import {
|
||||
closePages,
|
||||
closeSinglePage,
|
||||
firstPageOnTop,
|
||||
getSpanRectFromText,
|
||||
kbSelectAll,
|
||||
loadAndWait,
|
||||
scrollIntoView,
|
||||
waitForEvent,
|
||||
} from "./test_utils.mjs";
|
||||
import { MathClamp } from "../../src/shared/math_clamp.js";
|
||||
@ -1148,6 +1150,94 @@ describe("Text layer", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the page has been destroyed and rendered again", () => {
|
||||
const selectionSelector = ".canvasWrapper .selection svg path[d]";
|
||||
const timeout = 5000;
|
||||
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
`.page[data-page-number = "1"] .endOfContent`,
|
||||
undefined,
|
||||
undefined,
|
||||
{ annotationEditorMode: -1 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
async function selectSomeText(page) {
|
||||
const [positionStart, positionEnd] = await Promise.all([
|
||||
getSpanRectFromText(
|
||||
page,
|
||||
1,
|
||||
"(frequently executed) bytecode sequences, records"
|
||||
).then(middlePosition),
|
||||
getSpanRectFromText(
|
||||
page,
|
||||
1,
|
||||
"them, and compiles them to fast native code. We call such a se-"
|
||||
).then(belowEndPosition),
|
||||
]);
|
||||
|
||||
await page.mouse.move(positionStart.x, positionStart.y);
|
||||
await page.mouse.down();
|
||||
await moveInSteps(page, positionStart, positionEnd, 20);
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
it("must draw the selection", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await selectSomeText(page);
|
||||
await page.waitForSelector(selectionSelector, { timeout });
|
||||
|
||||
await page.evaluate(() => {
|
||||
document.getSelection().removeAllRanges();
|
||||
});
|
||||
await page.waitForSelector(selectionSelector, {
|
||||
hidden: true,
|
||||
timeout,
|
||||
});
|
||||
|
||||
// Scroll down, page by page, until the first page view is
|
||||
// evicted from the buffer: its canvas wrapper is then removed.
|
||||
const pagesCount = await page.evaluate(
|
||||
() => window.PDFViewerApplication.pagesCount
|
||||
);
|
||||
let isDestroyed = false;
|
||||
for (let i = 2; i <= pagesCount && !isDestroyed; i++) {
|
||||
const selector = `.page[data-page-number = "${i}"]`;
|
||||
await scrollIntoView(page, selector);
|
||||
await page.waitForSelector(
|
||||
`${selector} .canvasWrapper canvas`,
|
||||
{ timeout: 0 }
|
||||
);
|
||||
isDestroyed = !(await page.$(
|
||||
`.page[data-page-number = "1"] .canvasWrapper`
|
||||
));
|
||||
}
|
||||
|
||||
expect(isDestroyed)
|
||||
.withContext(`In ${browserName}, first page destroyed`)
|
||||
.toBeTrue();
|
||||
|
||||
await firstPageOnTop(page);
|
||||
await page.waitForSelector(
|
||||
`.page[data-page-number = "1"] .canvasWrapper canvas`,
|
||||
{ timeout: 0 }
|
||||
);
|
||||
await selectSomeText(page);
|
||||
await page.waitForSelector(selectionSelector, { timeout });
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("using selection carets", () => {
|
||||
|
||||
@ -370,6 +370,7 @@ const PDFViewerApplication = {
|
||||
// Set some specific preferences for tests.
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||
Object.assign(opts, {
|
||||
annotationEditorMode: x => parseInt(x, 10),
|
||||
capCanvasAreaFactor: x => parseInt(x, 10),
|
||||
docBaseUrl: x => x,
|
||||
enableAltText: x => x === "true",
|
||||
|
||||
@ -936,9 +936,14 @@ class PDFPageView extends BasePDFPageView {
|
||||
if (this.structTreeLayer && !this.textLayer) {
|
||||
this.structTreeLayer = null;
|
||||
}
|
||||
// The annotation editor layer and the draw layer keep references on the
|
||||
// text layer (the latter uses its div in order to render the selection),
|
||||
// hence they must be recreated too.
|
||||
if (
|
||||
this.annotationEditorLayer &&
|
||||
(!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)
|
||||
(!keepAnnotationEditorLayer ||
|
||||
!this.annotationEditorLayer.div ||
|
||||
!this.textLayer)
|
||||
) {
|
||||
if (this.drawLayer) {
|
||||
this.drawLayer.cancel();
|
||||
@ -947,6 +952,10 @@ class PDFPageView extends BasePDFPageView {
|
||||
this.annotationEditorLayer.cancel();
|
||||
this.annotationEditorLayer = null;
|
||||
}
|
||||
if (this.drawLayer && !this.textLayer) {
|
||||
this.drawLayer.cancel();
|
||||
this.drawLayer = null;
|
||||
}
|
||||
if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
|
||||
this.xfaLayer.cancel();
|
||||
this.xfaLayer = null;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user