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
				
			
		
			
				
	
	
		
			965 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			965 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Copyright 2020 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 os from "os";
 | 
						|
 | 
						|
const isMac = os.platform() === "darwin";
 | 
						|
 | 
						|
function loadAndWait(filename, selector, zoom, setups, options, viewport) {
 | 
						|
  return Promise.all(
 | 
						|
    global.integrationSessions.map(async session => {
 | 
						|
      const page = await session.browser.newPage();
 | 
						|
 | 
						|
      if (viewport) {
 | 
						|
        await page.setViewport(viewport);
 | 
						|
      }
 | 
						|
 | 
						|
      // In order to avoid errors because of checks which depend on
 | 
						|
      // a locale.
 | 
						|
      await page.evaluateOnNewDocument(() => {
 | 
						|
        Object.defineProperty(navigator, "language", {
 | 
						|
          get() {
 | 
						|
            return "en-US";
 | 
						|
          },
 | 
						|
        });
 | 
						|
        Object.defineProperty(navigator, "languages", {
 | 
						|
          get() {
 | 
						|
            return ["en-US", "en"];
 | 
						|
          },
 | 
						|
        });
 | 
						|
      });
 | 
						|
 | 
						|
      let app_options = "";
 | 
						|
      if (options) {
 | 
						|
        const optionsObject =
 | 
						|
          typeof options === "function"
 | 
						|
            ? await options(page, session.name)
 | 
						|
            : options;
 | 
						|
 | 
						|
        // Options must be handled in app.js::_parseHashParams.
 | 
						|
        for (const [key, value] of Object.entries(optionsObject)) {
 | 
						|
          app_options += `&${key}=${encodeURIComponent(value)}`;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      const fileParam = filename.startsWith("http")
 | 
						|
        ? filename
 | 
						|
        : `/test/pdfs/${filename}`;
 | 
						|
      const url = `${global.integrationBaseUrl}?file=${fileParam}#zoom=${zoom ?? "page-fit"}${app_options}`;
 | 
						|
 | 
						|
      if (setups) {
 | 
						|
        // page.evaluateOnNewDocument allows us to run code before the
 | 
						|
        // first js script is executed.
 | 
						|
        // The idea here is to set up some setters for PDFViewerApplication
 | 
						|
        // and EventBus, so we can inject some code to do whatever we want
 | 
						|
        // soon enough especially before the first event in the eventBus is
 | 
						|
        // dispatched.
 | 
						|
        const { prePageSetup, appSetup, earlySetup, eventBusSetup } = setups;
 | 
						|
        await prePageSetup?.(page);
 | 
						|
        if (earlySetup || appSetup || eventBusSetup) {
 | 
						|
          await page.evaluateOnNewDocument(
 | 
						|
            (eaSetup, aSetup, evSetup) => {
 | 
						|
              if (eaSetup) {
 | 
						|
                // eslint-disable-next-line no-eval
 | 
						|
                eval(`(${eaSetup})`)();
 | 
						|
              }
 | 
						|
              let app;
 | 
						|
              let eventBus;
 | 
						|
              Object.defineProperty(window, "PDFViewerApplication", {
 | 
						|
                get() {
 | 
						|
                  return app;
 | 
						|
                },
 | 
						|
                set(newValue) {
 | 
						|
                  app = newValue;
 | 
						|
                  if (aSetup) {
 | 
						|
                    // eslint-disable-next-line no-eval
 | 
						|
                    eval(`(${aSetup})`)(app);
 | 
						|
                  }
 | 
						|
                  Object.defineProperty(app, "eventBus", {
 | 
						|
                    get() {
 | 
						|
                      return eventBus;
 | 
						|
                    },
 | 
						|
                    set(newV) {
 | 
						|
                      eventBus = newV;
 | 
						|
                      if (evSetup) {
 | 
						|
                        // eslint-disable-next-line no-eval
 | 
						|
                        eval(`(${evSetup})`)(eventBus);
 | 
						|
                      }
 | 
						|
                    },
 | 
						|
                  });
 | 
						|
                },
 | 
						|
              });
 | 
						|
            },
 | 
						|
            earlySetup?.toString(),
 | 
						|
            appSetup?.toString(),
 | 
						|
            eventBusSetup?.toString()
 | 
						|
          );
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      await page.goto(url);
 | 
						|
      await setups?.postPageSetup?.(page);
 | 
						|
 | 
						|
      await page.bringToFront();
 | 
						|
      if (selector) {
 | 
						|
        await page.waitForSelector(selector, {
 | 
						|
          timeout: 0,
 | 
						|
        });
 | 
						|
      }
 | 
						|
      return [session.name, page];
 | 
						|
    })
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function createPromise(page, callback) {
 | 
						|
  return page.evaluateHandle(
 | 
						|
    // eslint-disable-next-line no-eval
 | 
						|
    cb => [new Promise(eval(`(${cb})`))],
 | 
						|
    callback.toString()
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function awaitPromise(promise) {
 | 
						|
  return promise.evaluate(([p]) => p);
 | 
						|
}
 | 
						|
 | 
						|
function closePages(pages) {
 | 
						|
  return Promise.all(pages.map(([_, page]) => closeSinglePage(page)));
 | 
						|
}
 | 
						|
 | 
						|
async function closeSinglePage(page) {
 | 
						|
  // Avoid to keep something from a previous test.
 | 
						|
  await page.evaluate(async () => {
 | 
						|
    await window.PDFViewerApplication.testingClose();
 | 
						|
    window.localStorage.clear();
 | 
						|
  });
 | 
						|
  await page.close({ runBeforeUnload: false });
 | 
						|
}
 | 
						|
 | 
						|
async function waitForSandboxTrip(page) {
 | 
						|
  const handle = await page.evaluateHandle(() => [
 | 
						|
    new Promise(resolve => {
 | 
						|
      window.addEventListener("sandboxtripend", resolve, { once: true });
 | 
						|
      window.PDFViewerApplication.pdfScriptingManager.sandboxTrip();
 | 
						|
    }),
 | 
						|
  ]);
 | 
						|
  await awaitPromise(handle);
 | 
						|
}
 | 
						|
 | 
						|
function waitForTimeout(milliseconds) {
 | 
						|
  /**
 | 
						|
   * Wait for the given number of milliseconds.
 | 
						|
   *
 | 
						|
   * Note that waiting for an arbitrary time in tests is discouraged because it
 | 
						|
   * can easily cause intermittent failures, which is why this functionality is
 | 
						|
   * no longer provided by Puppeteer 22+ and we have to implement it ourselves
 | 
						|
   * for the remaining callers in the integration tests. We should avoid
 | 
						|
   * creating new usages of this function; instead please refer to the better
 | 
						|
   * alternatives at https://github.com/puppeteer/puppeteer/pull/11780.
 | 
						|
   */
 | 
						|
  return new Promise(resolve => {
 | 
						|
    setTimeout(resolve, milliseconds);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function clearInput(page, selector, waitForInputEvent = false) {
 | 
						|
  const action = async () => {
 | 
						|
    await page.click(selector);
 | 
						|
    await kbSelectAll(page);
 | 
						|
    await page.keyboard.press("Backspace");
 | 
						|
    await page.waitForFunction(
 | 
						|
      `document.querySelector('${selector}').value === ""`
 | 
						|
    );
 | 
						|
  };
 | 
						|
  return waitForInputEvent
 | 
						|
    ? waitForEvent({
 | 
						|
        page,
 | 
						|
        eventName: "input",
 | 
						|
        action,
 | 
						|
        selector,
 | 
						|
      })
 | 
						|
    : action();
 | 
						|
}
 | 
						|
 | 
						|
async function waitAndClick(page, selector, clickOptions = {}) {
 | 
						|
  await page.waitForSelector(selector, { visible: true });
 | 
						|
  await page.click(selector, clickOptions);
 | 
						|
}
 | 
						|
 | 
						|
function waitForPointerUp(page) {
 | 
						|
  return createPromise(page, resolve => {
 | 
						|
    window.addEventListener("pointerup", resolve, { once: true });
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function getSelector(id) {
 | 
						|
  return `[data-element-id="${id}"]`;
 | 
						|
}
 | 
						|
 | 
						|
async function getRect(page, selector) {
 | 
						|
  // In Chrome something is wrong when serializing a `DomRect`,
 | 
						|
  // so we extract the values and return them ourselves.
 | 
						|
  await page.waitForSelector(selector, { visible: true });
 | 
						|
  return page.$eval(selector, el => {
 | 
						|
    const { x, y, width, height } = el.getBoundingClientRect();
 | 
						|
    return { x, y, width, height };
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function getQuerySelector(id) {
 | 
						|
  return `document.querySelector('${getSelector(id)}')`;
 | 
						|
}
 | 
						|
 | 
						|
function getComputedStyleSelector(id) {
 | 
						|
  return `getComputedStyle(${getQuerySelector(id)})`;
 | 
						|
}
 | 
						|
 | 
						|
function getEditorSelector(n) {
 | 
						|
  return `#pdfjs_internal_editor_${n}`;
 | 
						|
}
 | 
						|
 | 
						|
function getAnnotationSelector(id) {
 | 
						|
  return `[data-annotation-id="${id}"]`;
 | 
						|
}
 | 
						|
 | 
						|
async function getSpanRectFromText(page, pageNumber, text) {
 | 
						|
  await page.waitForSelector(
 | 
						|
    `.page[data-page-number="${pageNumber}"] > .textLayer .endOfContent`
 | 
						|
  );
 | 
						|
  return page.evaluate(
 | 
						|
    (number, content) => {
 | 
						|
      for (const el of document.querySelectorAll(
 | 
						|
        `.page[data-page-number="${number}"] > .textLayer span:not(:has(> span))`
 | 
						|
      )) {
 | 
						|
        if (el.textContent === content) {
 | 
						|
          const { x, y, width, height } = el.getBoundingClientRect();
 | 
						|
          return { x, y, width, height };
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return null;
 | 
						|
    },
 | 
						|
    pageNumber,
 | 
						|
    text
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function waitForEvent({
 | 
						|
  page,
 | 
						|
  eventName,
 | 
						|
  action,
 | 
						|
  selector = null,
 | 
						|
  validator = null,
 | 
						|
  timeout = 5000,
 | 
						|
}) {
 | 
						|
  const handle = await page.evaluateHandle(
 | 
						|
    (name, sel, validate, timeOut) => {
 | 
						|
      let callback = null,
 | 
						|
        timeoutId = null;
 | 
						|
      const element = sel ? document.querySelector(sel) : document;
 | 
						|
      return [
 | 
						|
        Promise.race([
 | 
						|
          new Promise(resolve => {
 | 
						|
            // The promise is resolved if the event fired in the context of the
 | 
						|
            // selector and, if a validator is defined, the event data satisfies
 | 
						|
            // the conditions of the validator function.
 | 
						|
            callback = e => {
 | 
						|
              if (timeoutId) {
 | 
						|
                clearTimeout(timeoutId);
 | 
						|
              }
 | 
						|
              // eslint-disable-next-line no-eval
 | 
						|
              resolve(validate ? eval(`(${validate})`)(e) : true);
 | 
						|
            };
 | 
						|
            element.addEventListener(name, callback, { once: true });
 | 
						|
          }),
 | 
						|
          new Promise(resolve => {
 | 
						|
            timeoutId = setTimeout(() => {
 | 
						|
              element.removeEventListener(name, callback);
 | 
						|
              resolve(null);
 | 
						|
            }, timeOut);
 | 
						|
          }),
 | 
						|
        ]),
 | 
						|
      ];
 | 
						|
    },
 | 
						|
    eventName,
 | 
						|
    selector,
 | 
						|
    validator ? validator.toString() : null,
 | 
						|
    timeout
 | 
						|
  );
 | 
						|
 | 
						|
  await action();
 | 
						|
 | 
						|
  const success = await awaitPromise(handle);
 | 
						|
  if (success === null) {
 | 
						|
    console.warn(
 | 
						|
      `waitForEvent: ${eventName} didn't trigger within the timeout`
 | 
						|
    );
 | 
						|
  } else if (!success) {
 | 
						|
    console.warn(`waitForEvent: ${eventName} triggered, but validation failed`);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function waitForStorageEntries(page, nEntries) {
 | 
						|
  return page.waitForFunction(
 | 
						|
    n => window.PDFViewerApplication.pdfDocument.annotationStorage.size === n,
 | 
						|
    {},
 | 
						|
    nEntries
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function waitForSerialized(page, nEntries) {
 | 
						|
  return page.waitForFunction(
 | 
						|
    n => {
 | 
						|
      try {
 | 
						|
        return (
 | 
						|
          (window.PDFViewerApplication.pdfDocument.annotationStorage
 | 
						|
            .serializable.map?.size ?? 0) === n
 | 
						|
        );
 | 
						|
      } catch {
 | 
						|
        // When serializing a stamp annotation with a SVG, the transfer
 | 
						|
        // can fail because of the SVG, so we just retry.
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    },
 | 
						|
    {},
 | 
						|
    nEntries
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function applyFunctionToEditor(page, editorId, func) {
 | 
						|
  return page.evaluate(
 | 
						|
    (id, f) => {
 | 
						|
      const editor =
 | 
						|
        window.PDFViewerApplication.pdfDocument.annotationStorage.getRawValue(
 | 
						|
          id
 | 
						|
        );
 | 
						|
      // eslint-disable-next-line no-eval
 | 
						|
      eval(`(${f})`)(editor);
 | 
						|
    },
 | 
						|
    editorId,
 | 
						|
    func.toString()
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function selectEditor(page, selector, count = 1) {
 | 
						|
  const editorRect = await getRect(page, selector);
 | 
						|
  await page.mouse.click(
 | 
						|
    editorRect.x + editorRect.width / 2,
 | 
						|
    editorRect.y + editorRect.height / 2,
 | 
						|
    { count }
 | 
						|
  );
 | 
						|
  await waitForSelectedEditor(page, selector);
 | 
						|
}
 | 
						|
 | 
						|
async function waitForSelectedEditor(page, selector) {
 | 
						|
  return page.waitForSelector(`${selector}.selectedEditor`);
 | 
						|
}
 | 
						|
 | 
						|
async function unselectEditor(page, selector) {
 | 
						|
  await page.keyboard.press("Escape");
 | 
						|
  await waitForUnselectedEditor(page, selector);
 | 
						|
}
 | 
						|
 | 
						|
async function waitForUnselectedEditor(page, selector) {
 | 
						|
  return page.waitForSelector(`${selector}:not(.selectedEditor)`);
 | 
						|
}
 | 
						|
 | 
						|
async function mockClipboard(pages) {
 | 
						|
  return Promise.all(
 | 
						|
    pages.map(async ([_, page]) => {
 | 
						|
      await page.evaluate(() => {
 | 
						|
        let data = null;
 | 
						|
        const clipboard = {
 | 
						|
          writeText: async text => (data = text),
 | 
						|
          readText: async () => data,
 | 
						|
        };
 | 
						|
        Object.defineProperty(navigator, "clipboard", { value: clipboard });
 | 
						|
      });
 | 
						|
    })
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function copy(page) {
 | 
						|
  await waitForEvent({
 | 
						|
    page,
 | 
						|
    eventName: "copy",
 | 
						|
    action: () => kbCopy(page),
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function copyToClipboard(page, data) {
 | 
						|
  await page.evaluate(async dat => {
 | 
						|
    const items = Object.create(null);
 | 
						|
    for (const [type, value] of Object.entries(dat)) {
 | 
						|
      if (value.startsWith("data:")) {
 | 
						|
        const resp = await fetch(value);
 | 
						|
        items[type] = await resp.blob();
 | 
						|
      } else {
 | 
						|
        items[type] = new Blob([value], { type });
 | 
						|
      }
 | 
						|
    }
 | 
						|
    await navigator.clipboard.write([new ClipboardItem(items)]);
 | 
						|
  }, data);
 | 
						|
}
 | 
						|
 | 
						|
async function paste(page) {
 | 
						|
  await waitForEvent({
 | 
						|
    page,
 | 
						|
    eventName: "paste",
 | 
						|
    action: () => kbPaste(page),
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function pasteFromClipboard(page, selector = null) {
 | 
						|
  const validator = e => e.clipboardData.items.length !== 0;
 | 
						|
  await waitForEvent({
 | 
						|
    page,
 | 
						|
    eventName: "paste",
 | 
						|
    action: () => kbPaste(page),
 | 
						|
    selector,
 | 
						|
    validator,
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function getSerialized(page, filter = undefined) {
 | 
						|
  const values = await page.evaluate(() => {
 | 
						|
    const { map } =
 | 
						|
      window.PDFViewerApplication.pdfDocument.annotationStorage.serializable;
 | 
						|
    if (!map) {
 | 
						|
      return [];
 | 
						|
    }
 | 
						|
    const vals = Array.from(map.values());
 | 
						|
    for (const value of vals) {
 | 
						|
      for (const [k, v] of Object.entries(value)) {
 | 
						|
        // Puppeteer don't serialize typed array correctly, so we convert them
 | 
						|
        // to arrays.
 | 
						|
        if (ArrayBuffer.isView(v)) {
 | 
						|
          value[k] = Array.from(v);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return vals;
 | 
						|
  });
 | 
						|
  return filter ? values.map(filter) : values;
 | 
						|
}
 | 
						|
 | 
						|
async function getFirstSerialized(page, filter = undefined) {
 | 
						|
  return (await getSerialized(page, filter))[0];
 | 
						|
}
 | 
						|
 | 
						|
function getAnnotationStorage(page) {
 | 
						|
  return page.evaluate(() =>
 | 
						|
    Object.fromEntries(
 | 
						|
      window.PDFViewerApplication.pdfDocument.annotationStorage.serializable
 | 
						|
        .map || []
 | 
						|
    )
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function waitForEntryInStorage(page, key, value, checker = (x, y) => x === y) {
 | 
						|
  return page.waitForFunction(
 | 
						|
    (k, v, c) => {
 | 
						|
      const { map } =
 | 
						|
        window.PDFViewerApplication.pdfDocument.annotationStorage.serializable;
 | 
						|
      // eslint-disable-next-line no-eval
 | 
						|
      return map && eval(`(${c})`)(JSON.stringify(map.get(k)), v);
 | 
						|
    },
 | 
						|
    {},
 | 
						|
    key,
 | 
						|
    JSON.stringify(value),
 | 
						|
    checker.toString()
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function getEditors(page, kind) {
 | 
						|
  return page.evaluate(aKind => {
 | 
						|
    const elements = document.querySelectorAll(`.${aKind}Editor`);
 | 
						|
    const results = [];
 | 
						|
    for (const { id } of elements) {
 | 
						|
      results.push(parseInt(id.split("_").at(-1)));
 | 
						|
    }
 | 
						|
    results.sort();
 | 
						|
    return results;
 | 
						|
  }, kind);
 | 
						|
}
 | 
						|
 | 
						|
function getEditorDimensions(page, selector) {
 | 
						|
  return page.evaluate(sel => {
 | 
						|
    const { style } = document.querySelector(sel);
 | 
						|
    return {
 | 
						|
      left: style.left,
 | 
						|
      top: style.top,
 | 
						|
      width: style.width,
 | 
						|
      height: style.height,
 | 
						|
    };
 | 
						|
  }, selector);
 | 
						|
}
 | 
						|
 | 
						|
async function serializeBitmapDimensions(page) {
 | 
						|
  await page.waitForFunction(() => {
 | 
						|
    try {
 | 
						|
      const map =
 | 
						|
        window.PDFViewerApplication.pdfDocument.annotationStorage.serializable
 | 
						|
          .map;
 | 
						|
      return !!map;
 | 
						|
    } catch {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  return page.evaluate(() => {
 | 
						|
    const { map } =
 | 
						|
      window.PDFViewerApplication.pdfDocument.annotationStorage.serializable;
 | 
						|
    return map
 | 
						|
      ? Array.from(map.values(), x => ({
 | 
						|
          width: x.bitmap.width,
 | 
						|
          height: x.bitmap.height,
 | 
						|
        }))
 | 
						|
      : [];
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function dragAndDrop(page, selector, translations, steps = 1) {
 | 
						|
  const rect = await getRect(page, selector);
 | 
						|
  const startX = rect.x + rect.width / 2;
 | 
						|
  const startY = rect.y + rect.height / 2;
 | 
						|
  await page.mouse.move(startX, startY);
 | 
						|
  await page.mouse.down();
 | 
						|
  for (const [tX, tY] of translations) {
 | 
						|
    await page.mouse.move(startX + tX, startY + tY, { steps });
 | 
						|
  }
 | 
						|
  await page.mouse.up();
 | 
						|
  await page.waitForSelector("#viewer:not(.noUserSelect)");
 | 
						|
}
 | 
						|
 | 
						|
function waitForPageChanging(page) {
 | 
						|
  return createPromise(page, resolve => {
 | 
						|
    window.PDFViewerApplication.eventBus.on("pagechanging", resolve, {
 | 
						|
      once: true,
 | 
						|
    });
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function waitForAnnotationEditorLayer(page) {
 | 
						|
  return createPromise(page, resolve => {
 | 
						|
    window.PDFViewerApplication.eventBus.on(
 | 
						|
      "annotationeditorlayerrendered",
 | 
						|
      resolve,
 | 
						|
      { once: true }
 | 
						|
    );
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function waitForAnnotationModeChanged(page) {
 | 
						|
  return createPromise(page, resolve => {
 | 
						|
    window.PDFViewerApplication.eventBus.on(
 | 
						|
      "annotationeditormodechanged",
 | 
						|
      resolve,
 | 
						|
      { once: true }
 | 
						|
    );
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function waitForPageRendered(page, pageNumber) {
 | 
						|
  return page.evaluateHandle(
 | 
						|
    number => [
 | 
						|
      new Promise(resolve => {
 | 
						|
        const { eventBus } = window.PDFViewerApplication;
 | 
						|
        eventBus.on("pagerendered", function handler(e) {
 | 
						|
          if (
 | 
						|
            !e.isDetailView &&
 | 
						|
            (number === undefined || e.pageNumber === number)
 | 
						|
          ) {
 | 
						|
            resolve();
 | 
						|
            eventBus.off("pagerendered", handler);
 | 
						|
          }
 | 
						|
        });
 | 
						|
      }),
 | 
						|
    ],
 | 
						|
    pageNumber
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function waitForEditorMovedInDOM(page) {
 | 
						|
  return createPromise(page, resolve => {
 | 
						|
    window.PDFViewerApplication.eventBus.on("editormovedindom", resolve, {
 | 
						|
      once: true,
 | 
						|
    });
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function scrollIntoView(page, selector) {
 | 
						|
  const handle = await page.evaluateHandle(
 | 
						|
    sel => [
 | 
						|
      new Promise(resolve => {
 | 
						|
        const container = document.getElementById("viewerContainer");
 | 
						|
        if (container.scrollHeight <= container.clientHeight) {
 | 
						|
          resolve();
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        container.addEventListener("scrollend", resolve, { once: true });
 | 
						|
        const element = document.querySelector(sel);
 | 
						|
        element.scrollIntoView({ behavior: "instant", block: "start" });
 | 
						|
      }),
 | 
						|
    ],
 | 
						|
    selector
 | 
						|
  );
 | 
						|
  return awaitPromise(handle);
 | 
						|
}
 | 
						|
 | 
						|
async function firstPageOnTop(page) {
 | 
						|
  const handle = await page.evaluateHandle(() => [
 | 
						|
    new Promise(resolve => {
 | 
						|
      const container = document.getElementById("viewerContainer");
 | 
						|
      if (container.scrollTop === 0 && container.scrollLeft === 0) {
 | 
						|
        resolve();
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      container.addEventListener("scrollend", resolve, { once: true });
 | 
						|
      container.scrollTo(0, 0);
 | 
						|
    }),
 | 
						|
  ]);
 | 
						|
  return awaitPromise(handle);
 | 
						|
}
 | 
						|
 | 
						|
async function setCaretAt(page, pageNumber, text, position) {
 | 
						|
  await page.evaluate(
 | 
						|
    (pageN, string, pos) => {
 | 
						|
      for (const el of document.querySelectorAll(
 | 
						|
        `.page[data-page-number="${pageN}"] > .textLayer > span`
 | 
						|
      )) {
 | 
						|
        if (el.textContent === string) {
 | 
						|
          window.getSelection().setPosition(el.firstChild, pos);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
    pageNumber,
 | 
						|
    text,
 | 
						|
    position
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
const modifier = isMac ? "Meta" : "Control";
 | 
						|
async function kbCopy(page) {
 | 
						|
  await page.keyboard.down(modifier);
 | 
						|
  await page.keyboard.press("c", { commands: ["Copy"] });
 | 
						|
  await page.keyboard.up(modifier);
 | 
						|
}
 | 
						|
async function kbPaste(page) {
 | 
						|
  await page.keyboard.down(modifier);
 | 
						|
  await page.keyboard.press("v", { commands: ["Paste"] });
 | 
						|
  await page.keyboard.up(modifier);
 | 
						|
}
 | 
						|
async function kbUndo(page) {
 | 
						|
  await page.keyboard.down(modifier);
 | 
						|
  await page.keyboard.press("z");
 | 
						|
  await page.keyboard.up(modifier);
 | 
						|
}
 | 
						|
async function kbRedo(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Meta");
 | 
						|
    await page.keyboard.down("Shift");
 | 
						|
    await page.keyboard.press("z");
 | 
						|
    await page.keyboard.up("Shift");
 | 
						|
    await page.keyboard.up("Meta");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("y");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
async function kbSelectAll(page) {
 | 
						|
  await page.keyboard.down(modifier);
 | 
						|
  await page.keyboard.press("a", { commands: ["SelectAll"] });
 | 
						|
  await page.keyboard.up(modifier);
 | 
						|
}
 | 
						|
async function kbModifierDown(page) {
 | 
						|
  await page.keyboard.down(modifier);
 | 
						|
}
 | 
						|
async function kbModifierUp(page) {
 | 
						|
  await page.keyboard.up(modifier);
 | 
						|
}
 | 
						|
async function kbGoToEnd(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Meta");
 | 
						|
    await page.keyboard.press("ArrowDown", {
 | 
						|
      commands: ["MoveToEndOfDocument"],
 | 
						|
    });
 | 
						|
    await page.keyboard.up("Meta");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("End");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
async function kbGoToBegin(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Meta");
 | 
						|
    await page.keyboard.press("ArrowUp", {
 | 
						|
      commands: ["MoveToBeginningOfDocument"],
 | 
						|
    });
 | 
						|
    await page.keyboard.up("Meta");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("Home");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
async function kbBigMoveLeft(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Shift");
 | 
						|
    await page.keyboard.press("ArrowLeft");
 | 
						|
    await page.keyboard.up("Shift");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("ArrowLeft");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
async function kbBigMoveRight(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Shift");
 | 
						|
    await page.keyboard.press("ArrowRight");
 | 
						|
    await page.keyboard.up("Shift");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("ArrowRight");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
async function kbBigMoveUp(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Shift");
 | 
						|
    await page.keyboard.press("ArrowUp");
 | 
						|
    await page.keyboard.up("Shift");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("ArrowUp");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
async function kbBigMoveDown(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Shift");
 | 
						|
    await page.keyboard.press("ArrowDown");
 | 
						|
    await page.keyboard.up("Shift");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("ArrowDown");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function kbDeleteLastWord(page) {
 | 
						|
  if (isMac) {
 | 
						|
    await page.keyboard.down("Alt");
 | 
						|
    await page.keyboard.press("Backspace");
 | 
						|
    await page.keyboard.up("Alt");
 | 
						|
  } else {
 | 
						|
    await page.keyboard.down("Control");
 | 
						|
    await page.keyboard.press("Backspace");
 | 
						|
    await page.keyboard.up("Control");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function kbFocusNext(page) {
 | 
						|
  const handle = await createPromise(page, resolve => {
 | 
						|
    window.addEventListener("focusin", resolve, { once: true });
 | 
						|
  });
 | 
						|
  await page.keyboard.press("Tab");
 | 
						|
  await awaitPromise(handle);
 | 
						|
}
 | 
						|
 | 
						|
async function kbFocusPrevious(page) {
 | 
						|
  const handle = await createPromise(page, resolve => {
 | 
						|
    window.addEventListener("focusin", resolve, { once: true });
 | 
						|
  });
 | 
						|
  await page.keyboard.down("Shift");
 | 
						|
  await page.keyboard.press("Tab");
 | 
						|
  await page.keyboard.up("Shift");
 | 
						|
  await awaitPromise(handle);
 | 
						|
}
 | 
						|
 | 
						|
async function kbSave(page) {
 | 
						|
  await page.keyboard.down(modifier);
 | 
						|
  await page.keyboard.press("s");
 | 
						|
  await page.keyboard.up(modifier);
 | 
						|
}
 | 
						|
 | 
						|
async function switchToEditor(name, page, disable = false) {
 | 
						|
  const modeChangedHandle = await createPromise(page, resolve => {
 | 
						|
    window.PDFViewerApplication.eventBus.on(
 | 
						|
      "annotationeditormodechanged",
 | 
						|
      resolve,
 | 
						|
      { once: true }
 | 
						|
    );
 | 
						|
  });
 | 
						|
  await page.click(`#editor${name}Button`);
 | 
						|
  name = name.toLowerCase();
 | 
						|
  await page.waitForSelector(
 | 
						|
    ".annotationEditorLayer" +
 | 
						|
      (disable ? `:not(.${name}Editing)` : `.${name}Editing`)
 | 
						|
  );
 | 
						|
  await awaitPromise(modeChangedHandle);
 | 
						|
}
 | 
						|
 | 
						|
async function selectEditors(name, page) {
 | 
						|
  await kbSelectAll(page);
 | 
						|
  await page.waitForFunction(
 | 
						|
    () => !document.querySelector(`.${name}Editor:not(.selectedEditor)`)
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function clearEditors(name, page) {
 | 
						|
  await selectEditors(name, page);
 | 
						|
  await page.keyboard.press("Backspace");
 | 
						|
  await waitForStorageEntries(page, 0);
 | 
						|
}
 | 
						|
 | 
						|
function waitForNoElement(page, selector) {
 | 
						|
  return page.waitForFunction(
 | 
						|
    sel => !document.querySelector(sel),
 | 
						|
    {},
 | 
						|
    selector
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function isCanvasMonochrome(page, pageNumber, rectangle, color) {
 | 
						|
  return page.evaluate(
 | 
						|
    (rect, pageN, col) => {
 | 
						|
      const canvas = document.querySelector(
 | 
						|
        `.page[data-page-number = "${pageN}"] .canvasWrapper canvas`
 | 
						|
      );
 | 
						|
      const canvasRect = canvas.getBoundingClientRect();
 | 
						|
      const ctx = canvas.getContext("2d");
 | 
						|
      rect ||= canvasRect;
 | 
						|
      const { data } = ctx.getImageData(
 | 
						|
        rect.x - canvasRect.x,
 | 
						|
        rect.y - canvasRect.y,
 | 
						|
        rect.width,
 | 
						|
        rect.height
 | 
						|
      );
 | 
						|
      return new Uint32Array(data.buffer).every(x => x === col);
 | 
						|
    },
 | 
						|
    rectangle,
 | 
						|
    pageNumber,
 | 
						|
    color
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function getXY(page, selector) {
 | 
						|
  const rect = await getRect(page, selector);
 | 
						|
  return `${rect.x}::${rect.y}`;
 | 
						|
}
 | 
						|
 | 
						|
function waitForPositionChange(page, selector, xy) {
 | 
						|
  return page.waitForFunction(
 | 
						|
    (sel, currentXY) => {
 | 
						|
      const bbox = document.querySelector(sel).getBoundingClientRect();
 | 
						|
      return `${bbox.x}::${bbox.y}` !== currentXY;
 | 
						|
    },
 | 
						|
    {},
 | 
						|
    selector,
 | 
						|
    xy
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function moveEditor(page, selector, n, pressKey) {
 | 
						|
  let xy = await getXY(page, selector);
 | 
						|
  for (let i = 0; i < n; i++) {
 | 
						|
    const handle = await waitForEditorMovedInDOM(page);
 | 
						|
    await pressKey();
 | 
						|
    await awaitPromise(handle);
 | 
						|
    await waitForPositionChange(page, selector, xy);
 | 
						|
    xy = await getXY(page, selector);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// Unicode bidi isolation characters, Fluent adds these markers to the text.
 | 
						|
const FSI = "\u2068";
 | 
						|
const PDI = "\u2069";
 | 
						|
 | 
						|
export {
 | 
						|
  applyFunctionToEditor,
 | 
						|
  awaitPromise,
 | 
						|
  clearEditors,
 | 
						|
  clearInput,
 | 
						|
  closePages,
 | 
						|
  closeSinglePage,
 | 
						|
  copy,
 | 
						|
  copyToClipboard,
 | 
						|
  createPromise,
 | 
						|
  dragAndDrop,
 | 
						|
  firstPageOnTop,
 | 
						|
  FSI,
 | 
						|
  getAnnotationSelector,
 | 
						|
  getAnnotationStorage,
 | 
						|
  getComputedStyleSelector,
 | 
						|
  getEditorDimensions,
 | 
						|
  getEditors,
 | 
						|
  getEditorSelector,
 | 
						|
  getFirstSerialized,
 | 
						|
  getQuerySelector,
 | 
						|
  getRect,
 | 
						|
  getSelector,
 | 
						|
  getSerialized,
 | 
						|
  getSpanRectFromText,
 | 
						|
  getXY,
 | 
						|
  isCanvasMonochrome,
 | 
						|
  kbBigMoveDown,
 | 
						|
  kbBigMoveLeft,
 | 
						|
  kbBigMoveRight,
 | 
						|
  kbBigMoveUp,
 | 
						|
  kbDeleteLastWord,
 | 
						|
  kbFocusNext,
 | 
						|
  kbFocusPrevious,
 | 
						|
  kbGoToBegin,
 | 
						|
  kbGoToEnd,
 | 
						|
  kbModifierDown,
 | 
						|
  kbModifierUp,
 | 
						|
  kbRedo,
 | 
						|
  kbSave,
 | 
						|
  kbSelectAll,
 | 
						|
  kbUndo,
 | 
						|
  loadAndWait,
 | 
						|
  mockClipboard,
 | 
						|
  moveEditor,
 | 
						|
  paste,
 | 
						|
  pasteFromClipboard,
 | 
						|
  PDI,
 | 
						|
  scrollIntoView,
 | 
						|
  selectEditor,
 | 
						|
  selectEditors,
 | 
						|
  serializeBitmapDimensions,
 | 
						|
  setCaretAt,
 | 
						|
  switchToEditor,
 | 
						|
  unselectEditor,
 | 
						|
  waitAndClick,
 | 
						|
  waitForAnnotationEditorLayer,
 | 
						|
  waitForAnnotationModeChanged,
 | 
						|
  waitForEntryInStorage,
 | 
						|
  waitForEvent,
 | 
						|
  waitForNoElement,
 | 
						|
  waitForPageChanging,
 | 
						|
  waitForPageRendered,
 | 
						|
  waitForPointerUp,
 | 
						|
  waitForSandboxTrip,
 | 
						|
  waitForSelectedEditor,
 | 
						|
  waitForSerialized,
 | 
						|
  waitForStorageEntries,
 | 
						|
  waitForTimeout,
 | 
						|
  waitForUnselectedEditor,
 | 
						|
};
 |