From 5493585b54caa468afeaad3b4641e09a6f18554c Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Fri, 13 Mar 2026 15:55:49 +0100 Subject: [PATCH 1/2] Try to fix few intermittents bug in reorganize_pages_spec integration tests --- test/integration/reorganize_pages_spec.mjs | 46 +++++++++++----------- test/integration/test_utils.mjs | 43 ++++++++++++++++++-- web/pdf_thumbnail_viewer.js | 4 +- 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/test/integration/reorganize_pages_spec.mjs b/test/integration/reorganize_pages_spec.mjs index fba151951af28..f0be6c59e1aa1 100644 --- a/test/integration/reorganize_pages_spec.mjs +++ b/test/integration/reorganize_pages_spec.mjs @@ -260,7 +260,10 @@ describe("Reorganize Pages View", () => { await waitForThumbnailVisible(page, 1); const rect2 = await getRect(page, getThumbnailSelector(2)); const rect4 = await getRect(page, getThumbnailSelector(4)); - await page.click(`.thumbnail:has(${getThumbnailSelector(1)}) input`); + await waitAndClick( + page, + `.thumbnail:has(${getThumbnailSelector(1)}) input` + ); const handlePagesEdited = await waitForPagesEdited(page); await dragAndDrop( @@ -288,10 +291,11 @@ describe("Reorganize Pages View", () => { const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); await (await page.$(".thumbnail[page-number='14'")).scrollIntoView(); - await page.waitForSelector(getThumbnailSelector(14), { - visible: true, - }); - await page.click(`.thumbnail:has(${getThumbnailSelector(14)}) input`); + + await waitAndClick( + page, + `.thumbnail:has(${getThumbnailSelector(14)}) input` + ); await (await page.$(".thumbnail[page-number='1'")).scrollIntoView(); await page.waitForSelector(getThumbnailSelector(1), { visible: true, @@ -323,7 +327,7 @@ describe("Reorganize Pages View", () => { const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); - await page.click(getThumbnailSelector(2)); + await waitAndClick(page, getThumbnailSelector(2)); await page.waitForSelector( `${getThumbnailSelector(2)}[aria-current="page"]` ); @@ -367,11 +371,8 @@ describe("Reorganize Pages View", () => { it("should check if the search is working after moving pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#viewFindButton"); - await page.waitForSelector(":has(> #findHighlightAll)", { - visible: true, - }); - await page.click(":has(> #findHighlightAll)"); + await waitAndClick(page, "#viewFindButton"); + await waitAndClick(page, ":has(> #findHighlightAll)"); await page.waitForSelector("#findInput", { visible: true }); await page.type("#findInput", "1"); @@ -482,7 +483,7 @@ describe("Reorganize Pages View", () => { await waitForThumbnailVisible(page, 1); await movePages(page, [2], 10); await scrollIntoView(page, getAnnotationSelector("107R")); - await page.click(getAnnotationSelector("107R")); + await waitAndClick(page, getAnnotationSelector("107R")); const currentPage = await page.$eval( "#pageNumber", el => el.valueAsNumber @@ -498,11 +499,11 @@ describe("Reorganize Pages View", () => { await waitForThumbnailVisible(page, 1); await movePages(page, [2, 4], 10); - await page.click("#viewsManagerSelectorButton"); - await page.click("#outlinesViewMenu"); + await waitAndClick(page, "#viewsManagerSelectorButton"); + await waitAndClick(page, "#outlinesViewMenu"); await page.waitForSelector("#outlinesView", { visible: true }); - await page.click("#outlinesView .treeItem:nth-child(2)"); + await waitAndClick(page, "#outlinesView .treeItem:nth-child(2)"); const currentPage = await page.$eval( "#pageNumber", @@ -539,12 +540,8 @@ describe("Reorganize Pages View", () => { await Promise.all( pages.map(async ([browserName, page]) => { await page.waitForSelector("#outlinesView", { visible: true }); - await page.waitForSelector("#viewsManagerSelectorButton", { - visible: true, - }); - await page.click("#viewsManagerSelectorButton"); - await page.waitForSelector("#thumbnailsViewMenu", { visible: true }); - await page.click("#thumbnailsViewMenu"); + await waitAndClick(page, "#viewsManagerSelectorButton"); + await waitAndClick(page, "#thumbnailsViewMenu"); const thumbSelector = "#thumbnailsView .thumbnailImageContainer > img"; @@ -1702,8 +1699,9 @@ describe("Reorganize Pages View", () => { await page.waitForSelector("button.thumbnailPasteButton", { hidden: true, }); - const pasteButtons = await page.$$("button.thumbnailPasteButton"); - expect(pasteButtons.length).withContext(`In ${browserName}`).toBe(0); + await page.waitForFunction( + () => !document.querySelector("button.thumbnailPasteButton") + ); }) ); }); @@ -1751,7 +1749,7 @@ describe("Reorganize Pages View", () => { ); }); - await page.click("#viewsManagerStatusActionButton"); + await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionExport"); const pagesData = await awaitPromise(handleExport); expect(pagesData) diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index cf382f0983334..4394118af884e 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -650,13 +650,50 @@ async function scrollIntoView(page, selector) { sel => [ new Promise(resolve => { const container = document.getElementById("viewerContainer"); - if (container.scrollHeight <= container.clientHeight) { + const element = document.querySelector(sel); + if (!container || !element) { resolve(); return; } - container.addEventListener("scrollend", resolve, { once: true }); - const element = document.querySelector(sel); + if ( + container.scrollHeight <= container.clientHeight && + container.scrollWidth <= container.clientWidth + ) { + resolve(); + return; + } + + const beforeTop = container.scrollTop; + const beforeLeft = container.scrollLeft; + let settled = false; + let timeoutId = null; + + const finish = () => { + if (settled) { + return; + } + settled = true; + if (timeoutId !== null) { + clearTimeout(timeoutId); + } + container.removeEventListener("scrollend", finish); + resolve(); + }; + + container.addEventListener("scrollend", finish, { once: true }); element.scrollIntoView({ behavior: "instant", block: "start" }); + + if ( + container.scrollTop === beforeTop && + container.scrollLeft === beforeLeft + ) { + finish(); + return; + } + + // Some browsers occasionally miss `scrollend`, so keep a short + // fallback to avoid hanging. + timeoutId = setTimeout(finish, 250); }), ], selector diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index dc763f48d9ea8..3bdf11f8bb6e0 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -721,7 +721,9 @@ class PDFThumbnailViewer { const newPageNumber = currentPageNumber || 1; this.linkService.goToPage(newPageNumber); const thumbnailView = this._thumbnails[newPageNumber - 1]; - thumbnailView.imageContainer.focus(); + if (!this.container.contains(document.activeElement)) { + thumbnailView.imageContainer.focus(); + } }, 0); } From fd1fea5f6a12074e4436948fb172f64c42ff8a77 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Tue, 17 Mar 2026 16:00:19 +0100 Subject: [PATCH 2/2] Remove some useless operations when getting the text content The removed code has been added in #20624 and it's useless since these operations (i.e. save/restore) are already handled in preprocessor.read. --- src/core/evaluator.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index eddde0788d4fe..3a93c086ffc9d 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -3197,6 +3197,9 @@ class PartialEvaluator { break; } + // preprocessor.read() already handles save, restore and transform + // operations, so we don't need to worry about them here. + textState = stateManager.state; currentTextState ||= textState.clone(); const fn = operation.fn; @@ -3570,12 +3573,6 @@ class PartialEvaluator { }); } break; - case OPS.restore: - stateManager.restore(); - break; - case OPS.save: - stateManager.save(); - break; } // switch if (textContent.items.length >= (sink?.desiredSize ?? 1)) { // Wait for ready, if we reach highWaterMark.