Some checks failed
		
		
	
	Types tests / Test (lts/*) (push) Has been cancelled
				
			Lint / Lint (lts/*) (push) Has been cancelled
				
			CodeQL / Analyze (javascript) (push) Has been cancelled
				
			CI / Test (20) (push) Has been cancelled
				
			CI / Test (22) (push) Has been cancelled
				
			CI / Test (24) (push) Has been cancelled
				
			
		
			
				
	
	
		
			1465 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1465 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Copyright 2024 Mozilla Foundation
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 */
 | 
						|
 | 
						|
import {
 | 
						|
  awaitPromise,
 | 
						|
  closePages,
 | 
						|
  createPromise,
 | 
						|
  getRect,
 | 
						|
  getSpanRectFromText,
 | 
						|
  loadAndWait,
 | 
						|
  scrollIntoView,
 | 
						|
  waitForPageChanging,
 | 
						|
  waitForPageRendered,
 | 
						|
} from "./test_utils.mjs";
 | 
						|
import { PNG } from "pngjs";
 | 
						|
 | 
						|
describe("PDF viewer", () => {
 | 
						|
  describe("Zoom origin", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait(
 | 
						|
        "tracemonkey.pdf",
 | 
						|
        ".textLayer .endOfContent",
 | 
						|
        "page-width",
 | 
						|
        null,
 | 
						|
        { page: 2 }
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    async function waitForTextAfterZoom(page, originX, originY, scale, text) {
 | 
						|
      const handlePromise = await createPromise(page, resolve => {
 | 
						|
        const callback = e => {
 | 
						|
          if (e.pageNumber === 2) {
 | 
						|
            window.PDFViewerApplication.eventBus.off(
 | 
						|
              "textlayerrendered",
 | 
						|
              callback
 | 
						|
            );
 | 
						|
            resolve();
 | 
						|
          }
 | 
						|
        };
 | 
						|
        window.PDFViewerApplication.eventBus.on("textlayerrendered", callback);
 | 
						|
      });
 | 
						|
 | 
						|
      await page.evaluate(
 | 
						|
        (scaleFactor, origin) => {
 | 
						|
          window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
            drawingDelay: 0,
 | 
						|
            scaleFactor,
 | 
						|
            origin,
 | 
						|
          });
 | 
						|
        },
 | 
						|
        scale,
 | 
						|
        [originX, originY]
 | 
						|
      );
 | 
						|
 | 
						|
      await awaitPromise(handlePromise);
 | 
						|
 | 
						|
      await page.waitForFunction(
 | 
						|
        `document.elementFromPoint(${originX}, ${originY})?.textContent === "${text}"`
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    it("supports specifying a custom origin", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          // We use this text span of page 2 because:
 | 
						|
          // - it's in the visible area even when zooming at page-width
 | 
						|
          // - it's small, so it easily catches if the page moves too much
 | 
						|
          // - it's in a "random" position: not near the center of the
 | 
						|
          //   viewport, and not near the borders
 | 
						|
          const text = "guards";
 | 
						|
 | 
						|
          const rect = await getSpanRectFromText(page, 2, text);
 | 
						|
          const originX = rect.x + rect.width / 2;
 | 
						|
          const originY = rect.y + rect.height / 2;
 | 
						|
 | 
						|
          await waitForTextAfterZoom(page, originX, originY, 2, text);
 | 
						|
          await waitForTextAfterZoom(page, originX, originY, 0.8, text);
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Zoom with the mouse wheel", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait("empty.pdf", ".textLayer .endOfContent", 1000);
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must check that we can zoom with the mouse wheel and pressed control key", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          await page.keyboard.down("Control");
 | 
						|
          let zoom = 10;
 | 
						|
          const zoomGetter = () =>
 | 
						|
            page.evaluate(
 | 
						|
              () => window.PDFViewerApplication.pdfViewer.currentScale
 | 
						|
            );
 | 
						|
          while (zoom > 0.1) {
 | 
						|
            await page.mouse.wheel({ deltaY: 100 });
 | 
						|
            zoom = await zoomGetter();
 | 
						|
          }
 | 
						|
          while (zoom < 10) {
 | 
						|
            await page.mouse.wheel({ deltaY: -100 });
 | 
						|
            zoom = await zoomGetter();
 | 
						|
          }
 | 
						|
          await page.keyboard.up("Control");
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Zoom commands", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait("tracemonkey.pdf", ".textLayer .endOfContent");
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must check that zoom commands don't scroll the document", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          for (let i = 0; i < 10; i++) {
 | 
						|
            await page.evaluate(() => window.PDFViewerApplication.zoomIn());
 | 
						|
            await page.evaluate(() => window.PDFViewerApplication.zoomReset());
 | 
						|
            await page.waitForSelector(
 | 
						|
              `.page[data-page-number="1"] .textLayer .endOfContent`
 | 
						|
            );
 | 
						|
            const scrollTop = await page.evaluate(
 | 
						|
              () => document.getElementById("viewerContainer").scrollTop
 | 
						|
            );
 | 
						|
            expect(scrollTop < 100)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBe(true);
 | 
						|
          }
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("CSS-only zoom", () => {
 | 
						|
    function createPromiseForFirstPageRendered(page) {
 | 
						|
      return createPromise(page, (resolve, reject) => {
 | 
						|
        const controller = new AbortController();
 | 
						|
        window.PDFViewerApplication.eventBus.on(
 | 
						|
          "pagerendered",
 | 
						|
          ({ pageNumber, timestamp }) => {
 | 
						|
            if (pageNumber === 1) {
 | 
						|
              resolve(timestamp);
 | 
						|
              controller.abort();
 | 
						|
            }
 | 
						|
          },
 | 
						|
          { signal: controller.signal }
 | 
						|
        );
 | 
						|
        setTimeout(reject, 1000, new Error("Timeout"));
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    describe("forced (maxCanvasPixels: 0)", () => {
 | 
						|
      let pages;
 | 
						|
 | 
						|
      beforeEach(async () => {
 | 
						|
        pages = await loadAndWait(
 | 
						|
          "tracemonkey.pdf",
 | 
						|
          ".textLayer .endOfContent",
 | 
						|
          null,
 | 
						|
          null,
 | 
						|
          { maxCanvasPixels: 0 }
 | 
						|
        );
 | 
						|
      });
 | 
						|
 | 
						|
      afterEach(async () => {
 | 
						|
        await closePages(pages);
 | 
						|
      });
 | 
						|
 | 
						|
      it("respects drawing delay when zooming out", async () => {
 | 
						|
        await Promise.all(
 | 
						|
          pages.map(async ([browserName, page]) => {
 | 
						|
            const promise = await createPromiseForFirstPageRendered(page);
 | 
						|
 | 
						|
            const start = await page.evaluate(() => {
 | 
						|
              const startTime = performance.now();
 | 
						|
              window.PDFViewerApplication.pdfViewer.decreaseScale({
 | 
						|
                drawingDelay: 100,
 | 
						|
                scaleFactor: 0.9,
 | 
						|
              });
 | 
						|
              return startTime;
 | 
						|
            });
 | 
						|
 | 
						|
            const end = await awaitPromise(promise);
 | 
						|
 | 
						|
            expect(end - start)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBeGreaterThanOrEqual(100);
 | 
						|
          })
 | 
						|
        );
 | 
						|
      });
 | 
						|
 | 
						|
      it("respects drawing delay when zooming in", async () => {
 | 
						|
        await Promise.all(
 | 
						|
          pages.map(async ([browserName, page]) => {
 | 
						|
            const promise = await createPromiseForFirstPageRendered(page);
 | 
						|
 | 
						|
            const start = await page.evaluate(() => {
 | 
						|
              const startTime = performance.now();
 | 
						|
              window.PDFViewerApplication.pdfViewer.increaseScale({
 | 
						|
                drawingDelay: 100,
 | 
						|
                scaleFactor: 1.1,
 | 
						|
              });
 | 
						|
              return startTime;
 | 
						|
            });
 | 
						|
 | 
						|
            const end = await awaitPromise(promise);
 | 
						|
 | 
						|
            expect(end - start)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBeGreaterThanOrEqual(100);
 | 
						|
          })
 | 
						|
        );
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("triggers when going bigger than maxCanvasPixels", () => {
 | 
						|
      let pages;
 | 
						|
 | 
						|
      const MAX_CANVAS_PIXELS = new Map();
 | 
						|
 | 
						|
      beforeEach(async () => {
 | 
						|
        pages = await loadAndWait(
 | 
						|
          "tracemonkey.pdf",
 | 
						|
          ".textLayer .endOfContent",
 | 
						|
          null,
 | 
						|
          null,
 | 
						|
          async (page, browserName) => {
 | 
						|
            const ratio = await page.evaluate(() => window.devicePixelRatio);
 | 
						|
            const maxCanvasPixels = 1_000_000 * ratio ** 2;
 | 
						|
            MAX_CANVAS_PIXELS.set(browserName, maxCanvasPixels);
 | 
						|
 | 
						|
            return { maxCanvasPixels };
 | 
						|
          }
 | 
						|
        );
 | 
						|
 | 
						|
        await Promise.all(
 | 
						|
          pages.map(async ([browserName, page]) => {
 | 
						|
            const handle = await waitForPageRendered(page);
 | 
						|
            if (
 | 
						|
              await page.evaluate(() => {
 | 
						|
                if (
 | 
						|
                  window.PDFViewerApplication.pdfViewer.currentScale !== 0.5
 | 
						|
                ) {
 | 
						|
                  window.PDFViewerApplication.pdfViewer.currentScale = 0.5;
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
              })
 | 
						|
            ) {
 | 
						|
              await awaitPromise(handle);
 | 
						|
            }
 | 
						|
          })
 | 
						|
        );
 | 
						|
      });
 | 
						|
 | 
						|
      afterEach(async () => {
 | 
						|
        await closePages(pages);
 | 
						|
      });
 | 
						|
 | 
						|
      function getCanvasSize(page) {
 | 
						|
        return page.evaluate(() => {
 | 
						|
          const canvas = window.document.querySelector(".canvasWrapper canvas");
 | 
						|
          return canvas.width * canvas.height;
 | 
						|
        });
 | 
						|
      }
 | 
						|
 | 
						|
      // MAX_CANVAS_PIXELS must be big enough that the originally rendered
 | 
						|
      // canvas still has enough space to grow before triggering CSS-only zoom
 | 
						|
      it("test correctly configured", async () => {
 | 
						|
        await Promise.all(
 | 
						|
          pages.map(async ([browserName, page]) => {
 | 
						|
            const canvasSize = await getCanvasSize(page);
 | 
						|
 | 
						|
            expect(canvasSize)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBeLessThan(MAX_CANVAS_PIXELS.get(browserName) / 4);
 | 
						|
            expect(canvasSize)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBeGreaterThan(MAX_CANVAS_PIXELS.get(browserName) / 16);
 | 
						|
          })
 | 
						|
        );
 | 
						|
      });
 | 
						|
 | 
						|
      it("does not trigger CSS-only zoom below maxCanvasPixels", async () => {
 | 
						|
        await Promise.all(
 | 
						|
          pages.map(async ([browserName, page]) => {
 | 
						|
            const originalCanvasSize = await getCanvasSize(page);
 | 
						|
            const factor = 2;
 | 
						|
 | 
						|
            const handle = await waitForPageRendered(page, 1);
 | 
						|
            await page.evaluate(scaleFactor => {
 | 
						|
              window.PDFViewerApplication.pdfViewer.increaseScale({
 | 
						|
                drawingDelay: 0,
 | 
						|
                scaleFactor,
 | 
						|
              });
 | 
						|
            }, factor);
 | 
						|
            await awaitPromise(handle);
 | 
						|
 | 
						|
            const canvasSize = await getCanvasSize(page);
 | 
						|
 | 
						|
            expect(canvasSize)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBe(originalCanvasSize * factor ** 2);
 | 
						|
 | 
						|
            expect(canvasSize)
 | 
						|
              .withContext(`In ${browserName}, MAX_CANVAS_PIXELS`)
 | 
						|
              .toBeLessThan(MAX_CANVAS_PIXELS.get(browserName));
 | 
						|
          })
 | 
						|
        );
 | 
						|
      });
 | 
						|
 | 
						|
      it("triggers CSS-only zoom above maxCanvasPixels", async () => {
 | 
						|
        await Promise.all(
 | 
						|
          pages.map(async ([browserName, page]) => {
 | 
						|
            const originalCanvasSize = await getCanvasSize(page);
 | 
						|
            const factor = 4;
 | 
						|
 | 
						|
            const handle = await waitForPageRendered(page, 1);
 | 
						|
            await page.evaluate(scaleFactor => {
 | 
						|
              window.PDFViewerApplication.pdfViewer.increaseScale({
 | 
						|
                drawingDelay: 0,
 | 
						|
                scaleFactor,
 | 
						|
              });
 | 
						|
            }, factor);
 | 
						|
            await awaitPromise(handle);
 | 
						|
 | 
						|
            const canvasSize = await getCanvasSize(page);
 | 
						|
 | 
						|
            expect(canvasSize)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBeLessThan(originalCanvasSize * factor ** 2);
 | 
						|
 | 
						|
            expect(canvasSize)
 | 
						|
              .withContext(`In ${browserName}, <= MAX_CANVAS_PIXELS / 4`)
 | 
						|
              .toBeLessThanOrEqual(MAX_CANVAS_PIXELS.get(browserName) / 4);
 | 
						|
 | 
						|
            expect(canvasSize)
 | 
						|
              .withContext(`In ${browserName}, > MAX_CANVAS_PIXELS / 4 * 0.95`)
 | 
						|
              .toBeGreaterThan((MAX_CANVAS_PIXELS.get(browserName) / 4) * 0.95);
 | 
						|
          })
 | 
						|
        );
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Canvas fits the page", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait(
 | 
						|
        "issue18694.pdf",
 | 
						|
        ".textLayer .endOfContent",
 | 
						|
        "page-width",
 | 
						|
        null,
 | 
						|
        { capCanvasAreaFactor: -1 }
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must check that canvas perfectly fits the page whatever the zoom level is", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          // The pdf has a single page with a red background.
 | 
						|
          // We set the viewer background to red, because when screenshoting
 | 
						|
          // some part of the viewer background can be visible.
 | 
						|
          // But here we don't care about the viewer background: we only
 | 
						|
          // care about the page background and the canvas default color.
 | 
						|
          await page.evaluate(() => {
 | 
						|
            document.body.style.background = "#ff0000";
 | 
						|
          });
 | 
						|
 | 
						|
          for (let i = 0; ; i++) {
 | 
						|
            const handle = await waitForPageRendered(page);
 | 
						|
            await page.evaluate(() => window.PDFViewerApplication.zoomOut());
 | 
						|
            await awaitPromise(handle);
 | 
						|
            await scrollIntoView(page, `.page[data-page-number="1"]`);
 | 
						|
 | 
						|
            const element = await page.$(`.page[data-page-number="1"]`);
 | 
						|
            const png = await element.screenshot({ type: "png" });
 | 
						|
            const pageImage = PNG.sync.read(Buffer.from(png));
 | 
						|
            let buffer = new Uint32Array(pageImage.data.buffer);
 | 
						|
 | 
						|
            // Search for the first red pixel.
 | 
						|
            const j = buffer.indexOf(0xff0000ff);
 | 
						|
            buffer = buffer.slice(j);
 | 
						|
 | 
						|
            expect(buffer.every(x => x === 0xff0000ff))
 | 
						|
              .withContext(`In ${browserName}, in the ${i}th zoom in`)
 | 
						|
              .toBe(true);
 | 
						|
 | 
						|
            const currentScale = await page.evaluate(
 | 
						|
              () => window.PDFViewerApplication.pdfViewer.currentScale
 | 
						|
            );
 | 
						|
            if (currentScale <= 0.1) {
 | 
						|
              break;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Detail view on zoom", () => {
 | 
						|
    const BASE_MAX_CANVAS_PIXELS = 1e6;
 | 
						|
 | 
						|
    function setupPages(
 | 
						|
      zoom,
 | 
						|
      devicePixelRatio,
 | 
						|
      capCanvasAreaFactor,
 | 
						|
      setups = {}
 | 
						|
    ) {
 | 
						|
      let pages;
 | 
						|
 | 
						|
      beforeEach(async () => {
 | 
						|
        pages = await loadAndWait(
 | 
						|
          "colors.pdf",
 | 
						|
          null,
 | 
						|
          zoom,
 | 
						|
          {
 | 
						|
            // When running Firefox with Puppeteer, setting the
 | 
						|
            // devicePixelRatio Puppeteer option does not properly set
 | 
						|
            // the `window.devicePixelRatio` value. Set it manually.
 | 
						|
            earlySetup: `() => {
 | 
						|
              window.devicePixelRatio = ${devicePixelRatio};
 | 
						|
            }`,
 | 
						|
            ...setups,
 | 
						|
          },
 | 
						|
          {
 | 
						|
            maxCanvasPixels: BASE_MAX_CANVAS_PIXELS * devicePixelRatio ** 2,
 | 
						|
            capCanvasAreaFactor,
 | 
						|
          },
 | 
						|
          { height: 600, width: 800, devicePixelRatio }
 | 
						|
        );
 | 
						|
      });
 | 
						|
 | 
						|
      afterEach(async () => {
 | 
						|
        await closePages(pages);
 | 
						|
      });
 | 
						|
 | 
						|
      return function forEachPage(fn) {
 | 
						|
        return Promise.all(
 | 
						|
          pages.map(([browserName, page]) => fn(browserName, page))
 | 
						|
        );
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    function extractCanvases(pageNumber) {
 | 
						|
      const pageOne = document.querySelector(
 | 
						|
        `.page[data-page-number='${pageNumber}']`
 | 
						|
      );
 | 
						|
      return Array.from(pageOne.querySelectorAll("canvas"), canvas => {
 | 
						|
        const { width, height } = canvas;
 | 
						|
        const ctx = canvas.getContext("2d");
 | 
						|
        const topLeft = ctx.getImageData(2, 2, 1, 1).data;
 | 
						|
        const bottomRight = ctx.getImageData(width - 3, height - 3, 1, 1).data;
 | 
						|
        return {
 | 
						|
          size: width * height,
 | 
						|
          width,
 | 
						|
          height,
 | 
						|
          topLeft: globalThis.pdfjsLib.Util.makeHexColor(...topLeft),
 | 
						|
          bottomRight: globalThis.pdfjsLib.Util.makeHexColor(...bottomRight),
 | 
						|
        };
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    function waitForDetailRendered(page) {
 | 
						|
      return createPromise(page, resolve => {
 | 
						|
        const controller = new AbortController();
 | 
						|
        window.PDFViewerApplication.eventBus.on(
 | 
						|
          "pagerendered",
 | 
						|
          ({ isDetailView }) => {
 | 
						|
            if (isDetailView) {
 | 
						|
              resolve();
 | 
						|
              controller.abort();
 | 
						|
            }
 | 
						|
          },
 | 
						|
          { signal: controller.signal }
 | 
						|
        );
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    for (const pixelRatio of [1, 2]) {
 | 
						|
      describe(`with pixel ratio ${pixelRatio}`, () => {
 | 
						|
        describe("setupPages()", () => {
 | 
						|
          const forEachPage = setupPages("100%", pixelRatio, -1);
 | 
						|
 | 
						|
          it("sets the proper devicePixelRatio", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              const devicePixelRatio = await page.evaluate(
 | 
						|
                () => window.devicePixelRatio
 | 
						|
              );
 | 
						|
 | 
						|
              expect(devicePixelRatio)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBe(pixelRatio);
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("when zooming with a cap on the canvas dimensions", () => {
 | 
						|
          const forEachPage = setupPages("10%", pixelRatio, 0);
 | 
						|
 | 
						|
          it("must render the detail view", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              await page.waitForSelector(
 | 
						|
                ".page[data-page-number='1'] .textLayer"
 | 
						|
              );
 | 
						|
 | 
						|
              const before = await page.evaluate(extractCanvases, 1);
 | 
						|
              expect(before.length)
 | 
						|
                .withContext(`In ${browserName}, before`)
 | 
						|
                .toBe(1);
 | 
						|
 | 
						|
              const factor = 50;
 | 
						|
              const handle = await waitForDetailRendered(page);
 | 
						|
              await page.evaluate(scaleFactor => {
 | 
						|
                window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
                  drawingDelay: 0,
 | 
						|
                  scaleFactor,
 | 
						|
                });
 | 
						|
              }, factor);
 | 
						|
              await awaitPromise(handle);
 | 
						|
 | 
						|
              const after = await page.evaluate(extractCanvases, 1);
 | 
						|
              // The page dimensions are 595x841, so the base canvas is a scale
 | 
						|
              // version of that but the number of pixels is capped to
 | 
						|
              // 800x600 = 480000.
 | 
						|
              expect(after.length)
 | 
						|
                .withContext(`In ${browserName}, after`)
 | 
						|
                .toBe(2);
 | 
						|
              expect(after[0].width)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBe(Math.floor(291 * pixelRatio));
 | 
						|
              expect(after[0].height)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBe(Math.floor(411.5 * pixelRatio));
 | 
						|
 | 
						|
              // The dimensions of the detail canvas are capped to 800x600 but
 | 
						|
              // it depends on the visible area which depends itself of the
 | 
						|
              // scrollbars dimensions, hence we just check that the canvas
 | 
						|
              // dimensions are capped.
 | 
						|
              expect(after[1].width)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBeLessThan(810 * pixelRatio);
 | 
						|
              expect(after[1].height)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBeLessThan(575 * pixelRatio);
 | 
						|
              expect(after[1].size)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBeLessThan(800 * 600 * pixelRatio ** 2);
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("when zooming in past max canvas size", () => {
 | 
						|
          const forEachPage = setupPages("100%", pixelRatio, -1);
 | 
						|
 | 
						|
          it("must render the detail view", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              await page.waitForSelector(
 | 
						|
                ".page[data-page-number='1'] .textLayer"
 | 
						|
              );
 | 
						|
 | 
						|
              const before = await page.evaluate(extractCanvases, 1);
 | 
						|
 | 
						|
              expect(before.length)
 | 
						|
                .withContext(`In ${browserName}, before`)
 | 
						|
                .toBe(1);
 | 
						|
              expect(before[0].size)
 | 
						|
                .withContext(`In ${browserName}, before`)
 | 
						|
                .toBeLessThan(BASE_MAX_CANVAS_PIXELS * pixelRatio ** 2);
 | 
						|
              expect(before[0])
 | 
						|
                .withContext(`In ${browserName}, before`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#85200c", // dark berry
 | 
						|
                    bottomRight: "#b6d7a8", // light green
 | 
						|
                  })
 | 
						|
                );
 | 
						|
 | 
						|
              const factor = 3;
 | 
						|
              // Check that we are going to trigger CSS zoom.
 | 
						|
              expect(before[0].size * factor ** 2)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBeGreaterThan(BASE_MAX_CANVAS_PIXELS * pixelRatio ** 2);
 | 
						|
 | 
						|
              const handle = await waitForDetailRendered(page);
 | 
						|
              await page.evaluate(scaleFactor => {
 | 
						|
                window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
                  drawingDelay: 0,
 | 
						|
                  scaleFactor,
 | 
						|
                });
 | 
						|
              }, factor);
 | 
						|
              await awaitPromise(handle);
 | 
						|
 | 
						|
              const after = await page.evaluate(extractCanvases, 1);
 | 
						|
 | 
						|
              expect(after.length)
 | 
						|
                .withContext(`In ${browserName}, after`)
 | 
						|
                .toBe(2);
 | 
						|
              expect(after[0].size)
 | 
						|
                .withContext(`In ${browserName}, after (first)`)
 | 
						|
                .toBeLessThan(4e6);
 | 
						|
              expect(after[0])
 | 
						|
                .withContext(`In ${browserName}, after (first)`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#85200c", // dark berry
 | 
						|
                    bottomRight: "#b6d7a8", // light green
 | 
						|
                  })
 | 
						|
                );
 | 
						|
              expect(after[1].size)
 | 
						|
                .withContext(`In ${browserName}, after (second)`)
 | 
						|
                .toBeLessThan(4e6);
 | 
						|
              expect(after[1])
 | 
						|
                .withContext(`In ${browserName}, after (second)`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#85200c", // dark berry
 | 
						|
                    bottomRight: "#ff0000", // bright red
 | 
						|
                  })
 | 
						|
                );
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("when starting already zoomed in past max canvas size", () => {
 | 
						|
          const forEachPage = setupPages("300%", pixelRatio, -1);
 | 
						|
 | 
						|
          it("must render the detail view", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              await page.waitForSelector(
 | 
						|
                ".page[data-page-number='1'] canvas:nth-child(2)"
 | 
						|
              );
 | 
						|
 | 
						|
              const canvases = await page.evaluate(extractCanvases, 1);
 | 
						|
 | 
						|
              expect(canvases.length).withContext(`In ${browserName}`).toBe(2);
 | 
						|
              expect(canvases[0].size)
 | 
						|
                .withContext(`In ${browserName} (first)`)
 | 
						|
                .toBeLessThan(4e6);
 | 
						|
              expect(canvases[0])
 | 
						|
                .withContext(`In ${browserName} (first)`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#85200c", // dark berry
 | 
						|
                    bottomRight: "#b6d7a8", // light green
 | 
						|
                  })
 | 
						|
                );
 | 
						|
              expect(canvases[1].size)
 | 
						|
                .withContext(`In ${browserName} (second)`)
 | 
						|
                .toBeLessThan(4e6);
 | 
						|
              expect(canvases[1])
 | 
						|
                .withContext(`In ${browserName} (second)`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#85200c", // dark berry
 | 
						|
                    bottomRight: "#ff0000", // bright red
 | 
						|
                  })
 | 
						|
                );
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("when scrolling", () => {
 | 
						|
          const forEachPage = setupPages("300%", pixelRatio, -1);
 | 
						|
 | 
						|
          it("must update the detail view", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              await page.waitForSelector(
 | 
						|
                ".page[data-page-number='1'] canvas:nth-child(2)"
 | 
						|
              );
 | 
						|
 | 
						|
              const handle = await waitForDetailRendered(page);
 | 
						|
              await page.evaluate(() => {
 | 
						|
                const container = document.getElementById("viewerContainer");
 | 
						|
                container.scrollTop += 1600;
 | 
						|
                container.scrollLeft += 1100;
 | 
						|
              });
 | 
						|
              await awaitPromise(handle);
 | 
						|
 | 
						|
              const canvases = await page.evaluate(extractCanvases, 1);
 | 
						|
 | 
						|
              expect(canvases.length).withContext(`In ${browserName}`).toBe(2);
 | 
						|
              expect(canvases[1].size)
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toBeLessThan(4e6);
 | 
						|
              expect(canvases[1])
 | 
						|
                .withContext(`In ${browserName}`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#ff9900", // bright orange
 | 
						|
                    bottomRight: "#ffe599", // light yellow
 | 
						|
                  })
 | 
						|
                );
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("when scrolling little enough that the existing detail covers the new viewport", () => {
 | 
						|
          const forEachPage = setupPages("300%", pixelRatio, -1);
 | 
						|
 | 
						|
          it("must not re-create the detail canvas", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              const detailCanvasSelector =
 | 
						|
                ".page[data-page-number='1'] canvas:nth-child(2)";
 | 
						|
 | 
						|
              await page.waitForSelector(detailCanvasSelector);
 | 
						|
 | 
						|
              const detailCanvasHandle = await page.$(detailCanvasSelector);
 | 
						|
 | 
						|
              let rendered = false;
 | 
						|
              const handle = await waitForDetailRendered(page);
 | 
						|
              await page.evaluate(() => {
 | 
						|
                const container = document.getElementById("viewerContainer");
 | 
						|
                container.scrollTop += 10;
 | 
						|
                container.scrollLeft += 10;
 | 
						|
              });
 | 
						|
              awaitPromise(handle)
 | 
						|
                .then(() => {
 | 
						|
                  rendered = true;
 | 
						|
                })
 | 
						|
                .catch(() => {});
 | 
						|
 | 
						|
              // Give some time to the page to re-render. If it re-renders it's
 | 
						|
              // a bug, but without waiting we would never catch it.
 | 
						|
              await new Promise(resolve => {
 | 
						|
                setTimeout(resolve, 100);
 | 
						|
              });
 | 
						|
 | 
						|
              const isSame = await page.evaluate(
 | 
						|
                (prev, selector) => prev === document.querySelector(selector),
 | 
						|
                detailCanvasHandle,
 | 
						|
                detailCanvasSelector
 | 
						|
              );
 | 
						|
 | 
						|
              expect(isSame).withContext(`In ${browserName}`).toBe(true);
 | 
						|
              expect(rendered).withContext(`In ${browserName}`).toBe(false);
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("when scrolling to have two visible pages", () => {
 | 
						|
          const forEachPage = setupPages("300%", pixelRatio, -1);
 | 
						|
 | 
						|
          it("must update the detail view", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              await page.waitForSelector(
 | 
						|
                ".page[data-page-number='1'] canvas:nth-child(2)"
 | 
						|
              );
 | 
						|
 | 
						|
              const handle = await createPromise(page, resolve => {
 | 
						|
                // wait for two 'pagerendered' events for detail views
 | 
						|
                let second = false;
 | 
						|
                const { eventBus } = window.PDFViewerApplication;
 | 
						|
                eventBus.on(
 | 
						|
                  "pagerendered",
 | 
						|
                  function onPageRendered({ isDetailView }) {
 | 
						|
                    if (!isDetailView) {
 | 
						|
                      return;
 | 
						|
                    }
 | 
						|
                    if (!second) {
 | 
						|
                      second = true;
 | 
						|
                      return;
 | 
						|
                    }
 | 
						|
                    eventBus.off("pagerendered", onPageRendered);
 | 
						|
                    resolve();
 | 
						|
                  }
 | 
						|
                );
 | 
						|
              });
 | 
						|
              await page.evaluate(() => {
 | 
						|
                const container = document.getElementById("viewerContainer");
 | 
						|
                container.scrollLeft += 600;
 | 
						|
                container.scrollTop += 3000;
 | 
						|
              });
 | 
						|
              await awaitPromise(handle);
 | 
						|
 | 
						|
              const [canvases1, canvases2] = await Promise.all([
 | 
						|
                page.evaluate(extractCanvases, 1),
 | 
						|
                page.evaluate(extractCanvases, 2),
 | 
						|
              ]);
 | 
						|
 | 
						|
              expect(canvases1.length)
 | 
						|
                .withContext(`In ${browserName}, first page`)
 | 
						|
                .toBe(2);
 | 
						|
              expect(canvases1[1].size)
 | 
						|
                .withContext(`In ${browserName}, first page`)
 | 
						|
                .toBeLessThan(4e6);
 | 
						|
              expect(canvases1[1])
 | 
						|
                .withContext(`In ${browserName}, first page`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#38761d", // dark green
 | 
						|
                    bottomRight: "#b6d7a8", // light green
 | 
						|
                  })
 | 
						|
                );
 | 
						|
 | 
						|
              expect(canvases2.length)
 | 
						|
                .withContext(`In ${browserName}, second page`)
 | 
						|
                .toBe(2);
 | 
						|
              expect(canvases2[1].size)
 | 
						|
                .withContext(`In ${browserName}, second page`)
 | 
						|
                .toBeLessThan(4e6);
 | 
						|
              expect(canvases2[1])
 | 
						|
                .withContext(`In ${browserName}, second page`)
 | 
						|
                .toEqual(
 | 
						|
                  jasmine.objectContaining({
 | 
						|
                    topLeft: "#134f5c", // dark cyan
 | 
						|
                    bottomRight: "#a2c4c9", // light cyan
 | 
						|
                  })
 | 
						|
                );
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("pagerendered event", () => {
 | 
						|
          const forEachPage = setupPages("100%", pixelRatio, -1, {
 | 
						|
            eventBusSetup: eventBus => {
 | 
						|
              globalThis.__pageRenderedEvents = [];
 | 
						|
 | 
						|
              eventBus.on(
 | 
						|
                "pagerendered",
 | 
						|
                ({ pageNumber, isDetailView, cssTransform }) => {
 | 
						|
                  globalThis.__pageRenderedEvents.push({
 | 
						|
                    pageNumber,
 | 
						|
                    isDetailView,
 | 
						|
                    cssTransform,
 | 
						|
                  });
 | 
						|
                }
 | 
						|
              );
 | 
						|
            },
 | 
						|
          });
 | 
						|
 | 
						|
          it("is dispatched properly", async () => {
 | 
						|
            await forEachPage(async (browserName, page) => {
 | 
						|
              const getPageRenderedEvents = () =>
 | 
						|
                page.evaluate(() => {
 | 
						|
                  const events = globalThis.__pageRenderedEvents;
 | 
						|
                  globalThis.__pageRenderedEvents = [];
 | 
						|
                  return events;
 | 
						|
                });
 | 
						|
              const waitForPageRenderedEvent = filter =>
 | 
						|
                page.waitForFunction(
 | 
						|
                  filterStr =>
 | 
						|
                    // eslint-disable-next-line no-eval
 | 
						|
                    globalThis.__pageRenderedEvents.some(eval(filterStr)),
 | 
						|
                  { polling: 50 },
 | 
						|
                  String(filter)
 | 
						|
                );
 | 
						|
 | 
						|
              // Initial render
 | 
						|
              await waitForPageRenderedEvent(e => e.pageNumber === 2);
 | 
						|
              expect(await getPageRenderedEvents())
 | 
						|
                .withContext(`In ${browserName}, initial render`)
 | 
						|
                .toEqual([
 | 
						|
                  { pageNumber: 1, isDetailView: false, cssTransform: false },
 | 
						|
                  { pageNumber: 2, isDetailView: false, cssTransform: false },
 | 
						|
                ]);
 | 
						|
 | 
						|
              // Zoom-in without triggering the detail view
 | 
						|
              await page.evaluate(() => {
 | 
						|
                window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
                  drawingDelay: -1,
 | 
						|
                  scaleFactor: 1.05,
 | 
						|
                });
 | 
						|
              });
 | 
						|
              await waitForPageRenderedEvent(e => e.pageNumber === 2);
 | 
						|
              expect(await getPageRenderedEvents())
 | 
						|
                .withContext(`In ${browserName}, first zoom`)
 | 
						|
                .toEqual([
 | 
						|
                  { pageNumber: 1, isDetailView: false, cssTransform: false },
 | 
						|
                  { pageNumber: 2, isDetailView: false, cssTransform: false },
 | 
						|
                ]);
 | 
						|
 | 
						|
              // Zoom-in on the first page, triggering the detail view
 | 
						|
              await page.evaluate(() => {
 | 
						|
                window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
                  drawingDelay: -1,
 | 
						|
                  scaleFactor: 2,
 | 
						|
                });
 | 
						|
              });
 | 
						|
              await Promise.all([
 | 
						|
                waitForPageRenderedEvent(
 | 
						|
                  e => e.isDetailView && e.pageNumber === 1
 | 
						|
                ),
 | 
						|
                waitForPageRenderedEvent(e => e.pageNumber === 2),
 | 
						|
              ]);
 | 
						|
              expect(await getPageRenderedEvents())
 | 
						|
                .withContext(`In ${browserName}, second zoom`)
 | 
						|
                .toEqual([
 | 
						|
                  { pageNumber: 1, isDetailView: false, cssTransform: false },
 | 
						|
                  { pageNumber: 1, isDetailView: true, cssTransform: false },
 | 
						|
                  { pageNumber: 2, isDetailView: false, cssTransform: false },
 | 
						|
                ]);
 | 
						|
 | 
						|
              // Zoom-in more
 | 
						|
              await page.evaluate(() => {
 | 
						|
                window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
                  drawingDelay: -1,
 | 
						|
                  scaleFactor: 2,
 | 
						|
                });
 | 
						|
              });
 | 
						|
              await Promise.all([
 | 
						|
                waitForPageRenderedEvent(
 | 
						|
                  e => e.isDetailView && e.pageNumber === 1
 | 
						|
                ),
 | 
						|
                waitForPageRenderedEvent(e => e.pageNumber === 2),
 | 
						|
              ]);
 | 
						|
              expect(await getPageRenderedEvents())
 | 
						|
                .withContext(`In ${browserName}, third zoom`)
 | 
						|
                .toEqual([
 | 
						|
                  { pageNumber: 1, isDetailView: false, cssTransform: true },
 | 
						|
                  { pageNumber: 2, isDetailView: false, cssTransform: true },
 | 
						|
                  { pageNumber: 1, isDetailView: true, cssTransform: false },
 | 
						|
                ]);
 | 
						|
 | 
						|
              // Scroll to another area of the first page
 | 
						|
              await page.evaluate(() => {
 | 
						|
                const container = document.getElementById("viewerContainer");
 | 
						|
                container.scrollTop += 1600;
 | 
						|
                container.scrollLeft += 1100;
 | 
						|
              });
 | 
						|
              await waitForPageRenderedEvent(e => e.isDetailView);
 | 
						|
              expect(await getPageRenderedEvents())
 | 
						|
                .withContext(`In ${browserName}, first scroll`)
 | 
						|
                .toEqual([
 | 
						|
                  { pageNumber: 1, isDetailView: true, cssTransform: false },
 | 
						|
                ]);
 | 
						|
 | 
						|
              // Scroll to the second page
 | 
						|
              await page.evaluate(() => {
 | 
						|
                const container = document.getElementById("viewerContainer");
 | 
						|
                const pageElement = document.querySelector(".page");
 | 
						|
                container.scrollTop +=
 | 
						|
                  pageElement.getBoundingClientRect().height;
 | 
						|
              });
 | 
						|
              await waitForPageRenderedEvent(e => e.isDetailView);
 | 
						|
              expect(await getPageRenderedEvents())
 | 
						|
                .withContext(`In ${browserName}, second scroll`)
 | 
						|
                .toEqual([
 | 
						|
                  { pageNumber: 2, isDetailView: true, cssTransform: false },
 | 
						|
                ]);
 | 
						|
 | 
						|
              // Zoom-out, to not have the detail view anymore
 | 
						|
              await page.evaluate(() => {
 | 
						|
                window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
                  drawingDelay: -1,
 | 
						|
                  scaleFactor: 0.25,
 | 
						|
                });
 | 
						|
              });
 | 
						|
              await Promise.all([
 | 
						|
                waitForPageRenderedEvent(e => e.pageNumber === 1),
 | 
						|
                waitForPageRenderedEvent(e => e.pageNumber === 2),
 | 
						|
              ]);
 | 
						|
              expect(await getPageRenderedEvents())
 | 
						|
                .withContext(`In ${browserName}, second zoom`)
 | 
						|
                .toEqual([
 | 
						|
                  { pageNumber: 2, isDetailView: false, cssTransform: false },
 | 
						|
                  { pageNumber: 1, isDetailView: false, cssTransform: false },
 | 
						|
                ]);
 | 
						|
 | 
						|
              const canvasesPerPage = await page.evaluate(() =>
 | 
						|
                Array.from(
 | 
						|
                  document.querySelectorAll(".canvasWrapper"),
 | 
						|
                  wrapper => wrapper.childElementCount
 | 
						|
                )
 | 
						|
              );
 | 
						|
              expect(canvasesPerPage)
 | 
						|
                .withContext(`In ${browserName}, number of canvases`)
 | 
						|
                .toEqual([1, 1]);
 | 
						|
            });
 | 
						|
          });
 | 
						|
        });
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    describe("when immediately cancelled and re-rendered", () => {
 | 
						|
      const forEachPage = setupPages("100%", 1, -1, {
 | 
						|
        eventBusSetup: eventBus => {
 | 
						|
          globalThis.__pageRenderedEvents = [];
 | 
						|
          eventBus.on("pagerendered", ({ pageNumber, isDetailView }) => {
 | 
						|
            globalThis.__pageRenderedEvents.push({ pageNumber, isDetailView });
 | 
						|
          });
 | 
						|
        },
 | 
						|
      });
 | 
						|
 | 
						|
      it("properly cleans up old canvases from the dom", async () => {
 | 
						|
        await forEachPage(async (browserName, page) => {
 | 
						|
          const waitForPageRenderedEvent = filter =>
 | 
						|
            page.waitForFunction(
 | 
						|
              filterStr => {
 | 
						|
                // eslint-disable-next-line no-eval
 | 
						|
                if (globalThis.__pageRenderedEvents.some(eval(filterStr))) {
 | 
						|
                  globalThis.__pageRenderedEvents = [];
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
              },
 | 
						|
              { polling: 50 },
 | 
						|
              String(filter)
 | 
						|
            );
 | 
						|
          const getCanvasCount = () =>
 | 
						|
            page.evaluate(
 | 
						|
              () =>
 | 
						|
                document.querySelector("[data-page-number='1'] .canvasWrapper")
 | 
						|
                  .childElementCount
 | 
						|
            );
 | 
						|
 | 
						|
          await waitForPageRenderedEvent(e => e.pageNumber === 1);
 | 
						|
 | 
						|
          await page.evaluate(() => {
 | 
						|
            window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
              drawingDelay: -1,
 | 
						|
              scaleFactor: 4,
 | 
						|
            });
 | 
						|
          });
 | 
						|
          await waitForPageRenderedEvent(
 | 
						|
            e => e.pageNumber === 1 && e.isDetailView
 | 
						|
          );
 | 
						|
          expect(await getCanvasCount())
 | 
						|
            .withContext(`In ${browserName}, after the first zoom-in`)
 | 
						|
            .toBe(2);
 | 
						|
 | 
						|
          await page.evaluate(() => {
 | 
						|
            window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
              drawingDelay: -1,
 | 
						|
              scaleFactor: 0.75,
 | 
						|
            });
 | 
						|
            window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
              drawingDelay: -1,
 | 
						|
              scaleFactor: 0.25,
 | 
						|
            });
 | 
						|
          });
 | 
						|
          await waitForPageRenderedEvent(e => e.pageNumber === 1);
 | 
						|
          expect(await getCanvasCount())
 | 
						|
            .withContext(`In ${browserName}, after the two zoom-out`)
 | 
						|
            .toBe(1);
 | 
						|
        });
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("when cancelled and re-rendered after 1 microtick", () => {
 | 
						|
      const forEachPage = setupPages("100%", 1, -1, {
 | 
						|
        eventBusSetup: eventBus => {
 | 
						|
          globalThis.__pageRenderedEvents = [];
 | 
						|
          eventBus.on("pagerendered", ({ pageNumber, isDetailView }) => {
 | 
						|
            globalThis.__pageRenderedEvents.push({ pageNumber, isDetailView });
 | 
						|
          });
 | 
						|
        },
 | 
						|
      });
 | 
						|
 | 
						|
      it("properly cleans up old canvases from the dom", async () => {
 | 
						|
        await forEachPage(async (browserName, page) => {
 | 
						|
          const waitForPageRenderedEvent = filter =>
 | 
						|
            page.waitForFunction(
 | 
						|
              filterStr => {
 | 
						|
                // eslint-disable-next-line no-eval
 | 
						|
                if (globalThis.__pageRenderedEvents.some(eval(filterStr))) {
 | 
						|
                  globalThis.__pageRenderedEvents = [];
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
              },
 | 
						|
              { polling: 50 },
 | 
						|
              String(filter)
 | 
						|
            );
 | 
						|
          const getCanvasCount = () =>
 | 
						|
            page.evaluate(
 | 
						|
              () =>
 | 
						|
                document.querySelector("[data-page-number='1'] .canvasWrapper")
 | 
						|
                  .childElementCount
 | 
						|
            );
 | 
						|
 | 
						|
          await waitForPageRenderedEvent(e => e.pageNumber === 1);
 | 
						|
 | 
						|
          await page.evaluate(() => {
 | 
						|
            window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
              drawingDelay: -1,
 | 
						|
              scaleFactor: 4,
 | 
						|
            });
 | 
						|
          });
 | 
						|
          await waitForPageRenderedEvent(
 | 
						|
            e => e.pageNumber === 1 && e.isDetailView
 | 
						|
          );
 | 
						|
          expect(await getCanvasCount())
 | 
						|
            .withContext(`In ${browserName}, after the first zoom-in`)
 | 
						|
            .toBe(2);
 | 
						|
 | 
						|
          await page.evaluate(() => {
 | 
						|
            window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
              drawingDelay: -1,
 | 
						|
              scaleFactor: 0.75,
 | 
						|
            });
 | 
						|
            Promise.resolve().then(() => {
 | 
						|
              window.PDFViewerApplication.pdfViewer.updateScale({
 | 
						|
                drawingDelay: -1,
 | 
						|
                scaleFactor: 0.25,
 | 
						|
              });
 | 
						|
            });
 | 
						|
          });
 | 
						|
          await waitForPageRenderedEvent(e => e.pageNumber === 1);
 | 
						|
          expect(await getCanvasCount())
 | 
						|
            .withContext(`In ${browserName}, after the two zoom-out`)
 | 
						|
            .toBe(1);
 | 
						|
        });
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("SecondaryToolbar", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    function normalizeRotation(rotation) {
 | 
						|
      return ((rotation % 360) + 360) % 360;
 | 
						|
    }
 | 
						|
 | 
						|
    function waitForRotationChanging(page, pagesRotation) {
 | 
						|
      return page.evaluateHandle(
 | 
						|
        rotation => [
 | 
						|
          new Promise(resolve => {
 | 
						|
            const { eventBus } = window.PDFViewerApplication;
 | 
						|
            eventBus.on("rotationchanging", function handler(e) {
 | 
						|
              if (rotation === undefined || e.pagesRotation === rotation) {
 | 
						|
                resolve();
 | 
						|
                eventBus.off("rotationchanging", handler);
 | 
						|
              }
 | 
						|
            });
 | 
						|
          }),
 | 
						|
        ],
 | 
						|
        normalizeRotation(pagesRotation)
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait("issue18694.pdf", ".textLayer .endOfContent");
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must check that the SecondaryToolbar doesn't close between rotations", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          await page.click("#secondaryToolbarToggleButton");
 | 
						|
          await page.waitForSelector("#secondaryToolbar", { hidden: false });
 | 
						|
 | 
						|
          for (let i = 1; i <= 4; i++) {
 | 
						|
            const secondaryToolbarIsOpen = await page.evaluate(
 | 
						|
              () => window.PDFViewerApplication.secondaryToolbar.isOpen
 | 
						|
            );
 | 
						|
            expect(secondaryToolbarIsOpen)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBeTrue();
 | 
						|
 | 
						|
            const rotation = i * 90;
 | 
						|
            const handle = await waitForRotationChanging(page, rotation);
 | 
						|
 | 
						|
            await page.click("#pageRotateCw");
 | 
						|
            await awaitPromise(handle);
 | 
						|
 | 
						|
            const pagesRotation = await page.evaluate(
 | 
						|
              () => window.PDFViewerApplication.pdfViewer.pagesRotation
 | 
						|
            );
 | 
						|
            expect(pagesRotation)
 | 
						|
              .withContext(`In ${browserName}`)
 | 
						|
              .toBe(normalizeRotation(rotation));
 | 
						|
          }
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Filename with a hash sign", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait("empty%23hash.pdf", ".textLayer .endOfContent");
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must extract the filename correctly", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          const filename = await page.evaluate(() => document.title);
 | 
						|
 | 
						|
          expect(filename)
 | 
						|
            .withContext(`In ${browserName}`)
 | 
						|
            .toBe("empty#hash.pdf");
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("File param with an URL", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      const baseURL = new URL(global.integrationBaseUrl);
 | 
						|
      const url = `${baseURL.origin}/build/generic/web/compressed.tracemonkey-pldi-09.pdf`;
 | 
						|
      pages = await loadAndWait(
 | 
						|
        encodeURIComponent(url),
 | 
						|
        ".textLayer .endOfContent"
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must load and extract the filename correctly", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          const filename = await page.evaluate(() => document.title);
 | 
						|
 | 
						|
          expect(filename)
 | 
						|
            .withContext(`In ${browserName}`)
 | 
						|
            .toBe("compressed.tracemonkey-pldi-09.pdf");
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Keyboard scrolling on startup (bug 843653)", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait("tracemonkey.pdf", ".textLayer .endOfContent");
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must check that keyboard scrolling works without having to give the focus to the viewer", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          const pdfViewer = await page.evaluateHandle(
 | 
						|
            () => window.PDFViewerApplication.pdfViewer
 | 
						|
          );
 | 
						|
 | 
						|
          // The viewer should not have the focus.
 | 
						|
          const hasFocus = await pdfViewer.evaluate(viewer =>
 | 
						|
            viewer.container.contains(document.activeElement)
 | 
						|
          );
 | 
						|
          expect(hasFocus).withContext(`In ${browserName}`).toBeFalse();
 | 
						|
 | 
						|
          let currentPageNumber = await pdfViewer.evaluate(
 | 
						|
            viewer => viewer.currentPageNumber
 | 
						|
          );
 | 
						|
          expect(currentPageNumber).withContext(`In ${browserName}`).toBe(1);
 | 
						|
 | 
						|
          // Press the 'PageDown' key to check that it works.
 | 
						|
          const handle = await waitForPageChanging(page);
 | 
						|
          await page.keyboard.press("PageDown");
 | 
						|
          await awaitPromise(handle);
 | 
						|
 | 
						|
          // The second page should be displayed.
 | 
						|
          currentPageNumber = await pdfViewer.evaluate(
 | 
						|
            viewer => viewer.currentPageNumber
 | 
						|
          );
 | 
						|
          expect(currentPageNumber).withContext(`In ${browserName}`).toBe(2);
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Printing can be disallowed for some pdfs (bug 1978985)", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait(
 | 
						|
        "print_protection.pdf",
 | 
						|
        "#passwordDialog",
 | 
						|
        null,
 | 
						|
        null,
 | 
						|
        { enablePermissions: true }
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(async () => {
 | 
						|
      await closePages(pages);
 | 
						|
    });
 | 
						|
 | 
						|
    it("must check that printing is disallowed", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          await page.waitForSelector("#printButton", {
 | 
						|
            visible: true,
 | 
						|
          });
 | 
						|
 | 
						|
          const selector = "#passwordDialog input#password";
 | 
						|
          await page.waitForSelector(selector, { visible: true });
 | 
						|
          await page.type(selector, "1234");
 | 
						|
          await page.click("#passwordDialog button#passwordSubmit");
 | 
						|
 | 
						|
          await page.waitForSelector(".textLayer .endOfContent");
 | 
						|
 | 
						|
          // The print button should be hidden.
 | 
						|
          await page.waitForSelector("#printButton", {
 | 
						|
            hidden: true,
 | 
						|
          });
 | 
						|
          await page.waitForSelector("#secondaryPrint", {
 | 
						|
            hidden: true,
 | 
						|
          });
 | 
						|
 | 
						|
          const hasThrown = await page.evaluate(() => {
 | 
						|
            try {
 | 
						|
              window.print();
 | 
						|
            } catch {
 | 
						|
              return true;
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
          });
 | 
						|
          expect(hasThrown).withContext(`In ${browserName}`).toBeTrue();
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Pinch-zoom", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait(
 | 
						|
        "tracemonkey.pdf",
 | 
						|
        `.page[data-page-number = "1"] .endOfContent`
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
    it("keeps the content under the pinch centre fixed on the screen", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          if (browserName === "firefox") {
 | 
						|
            pending(
 | 
						|
              "Touch events are not supported on devices without touch screen in Firefox."
 | 
						|
            );
 | 
						|
          }
 | 
						|
          if (browserName === "chrome") {
 | 
						|
            pending(
 | 
						|
              "Pinch zoom emulation is not supported for WebDriver BiDi in Chrome."
 | 
						|
            );
 | 
						|
          }
 | 
						|
 | 
						|
          const rect = await getSpanRectFromText(page, 1, "type-stable");
 | 
						|
          const originX = rect.x + rect.width / 2;
 | 
						|
          const originY = rect.y + rect.height / 2;
 | 
						|
          const rendered = await createPromise(page, resolve => {
 | 
						|
            const cb = e => {
 | 
						|
              if (e.pageNumber === 1) {
 | 
						|
                window.PDFViewerApplication.eventBus.off(
 | 
						|
                  "textlayerrendered",
 | 
						|
                  cb
 | 
						|
                );
 | 
						|
                resolve();
 | 
						|
              }
 | 
						|
            };
 | 
						|
            window.PDFViewerApplication.eventBus.on("textlayerrendered", cb);
 | 
						|
          });
 | 
						|
          const client = await page.target().createCDPSession();
 | 
						|
          await client.send("Input.synthesizePinchGesture", {
 | 
						|
            x: originX,
 | 
						|
            y: originY,
 | 
						|
            scaleFactor: 3,
 | 
						|
            gestureSourceType: "touch",
 | 
						|
          });
 | 
						|
          await awaitPromise(rendered);
 | 
						|
          const spanHandle = await page.evaluateHandle(() =>
 | 
						|
            Array.from(
 | 
						|
              document.querySelectorAll(
 | 
						|
                '.page[data-page-number="1"] .textLayer span'
 | 
						|
              )
 | 
						|
            ).find(span => span.textContent.includes("type-stable"))
 | 
						|
          );
 | 
						|
          expect(await spanHandle.isIntersectingViewport()).toBeTrue();
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe("Scroll into view", () => {
 | 
						|
    let pages;
 | 
						|
 | 
						|
    beforeEach(async () => {
 | 
						|
      pages = await loadAndWait(
 | 
						|
        "tracemonkey_annotation_on_page_8.pdf",
 | 
						|
        `.page[data-page-number = "1"] .endOfContent`
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
    it("Check that the top right corner of the annotation is centered vertically", async () => {
 | 
						|
      await Promise.all(
 | 
						|
        pages.map(async ([browserName, page]) => {
 | 
						|
          const handle = await page.evaluateHandle(() => [
 | 
						|
            new Promise(resolve => {
 | 
						|
              const container = document.getElementById("viewerContainer");
 | 
						|
              container.addEventListener("scrollend", resolve, {
 | 
						|
                once: true,
 | 
						|
              });
 | 
						|
              window.PDFViewerApplication.pdfLinkService.goToXY(
 | 
						|
                8,
 | 
						|
                43.55,
 | 
						|
                198.36,
 | 
						|
                {
 | 
						|
                  center: "vertical",
 | 
						|
                }
 | 
						|
              );
 | 
						|
            }),
 | 
						|
          ]);
 | 
						|
          await awaitPromise(handle);
 | 
						|
          const annotationSelector =
 | 
						|
            ".page[data-page-number='8'] .stampAnnotation";
 | 
						|
          await page.waitForSelector(annotationSelector, { visible: true });
 | 
						|
          const rect = await getRect(page, annotationSelector);
 | 
						|
          const containerRect = await getRect(page, "#viewerContainer");
 | 
						|
          expect(
 | 
						|
            Math.abs(2 * (rect.y - containerRect.y) - containerRect.height)
 | 
						|
          )
 | 
						|
            .withContext(`In ${browserName}`)
 | 
						|
            .toBeLessThan(1);
 | 
						|
        })
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 |