Break import cycles, in the viewer, for PDFViewerApplication

Currently the `web/app.js` file pulls in various build-specific dependencies, via the use of import maps, and those files in turn import from `web/app.js` thus creating undesirable import cycles.
To avoid this we instead pass in a `PDFViewerApplication`-reference, immediately after it's been created, to the relevant code.

Note that we use an ESLint plugin rule, see `import/no-cycle`, that is normally able to catch import cycles. However, in this case import maps are involved which is why this wasn't caught.
This commit is contained in:
Jonas Jenwald 2024-02-09 12:01:59 +01:00
parent 6da9448f6c
commit e98b9b019a
6 changed files with 49 additions and 34 deletions

View File

@ -53,12 +53,12 @@ import {
} from "pdfjs-lib"; } from "pdfjs-lib";
import { AppOptions, OptionKind } from "./app_options.js"; import { AppOptions, OptionKind } from "./app_options.js";
import { AutomationEventBus, EventBus } from "./event_utils.js"; import { AutomationEventBus, EventBus } from "./event_utils.js";
import { ExternalServices, initCom } from "web-external_services";
import { LinkTarget, PDFLinkService } from "./pdf_link_service.js"; import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
import { AltTextManager } from "web-alt_text_manager"; import { AltTextManager } from "web-alt_text_manager";
import { AnnotationEditorParams } from "web-annotation_editor_params"; import { AnnotationEditorParams } from "web-annotation_editor_params";
import { CaretBrowsingMode } from "./caret_browsing.js"; import { CaretBrowsingMode } from "./caret_browsing.js";
import { DownloadManager } from "web-download_manager"; import { DownloadManager } from "web-download_manager";
import { ExternalServices } from "web-external_services";
import { OverlayManager } from "./overlay_manager.js"; import { OverlayManager } from "./overlay_manager.js";
import { PasswordPrompt } from "./password_prompt.js"; import { PasswordPrompt } from "./password_prompt.js";
import { PDFAttachmentViewer } from "web-pdf_attachment_viewer"; import { PDFAttachmentViewer } from "web-pdf_attachment_viewer";
@ -2146,6 +2146,12 @@ const PDFViewerApplication = {
}, },
}; };
initCom(PDFViewerApplication);
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
PDFPrintServiceFactory.initGlobals(PDFViewerApplication);
}
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
const HOSTED_VIEWER_ORIGINS = [ const HOSTED_VIEWER_ORIGINS = [
"null", "null",

View File

@ -19,7 +19,6 @@ import { BaseExternalServices } from "./external_services.js";
import { BasePreferences } from "./preferences.js"; import { BasePreferences } from "./preferences.js";
import { GenericL10n } from "./genericl10n.js"; import { GenericL10n } from "./genericl10n.js";
import { GenericScripting } from "./generic_scripting.js"; import { GenericScripting } from "./generic_scripting.js";
import { PDFViewerApplication } from "./app.js";
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
throw new Error( throw new Error(
@ -42,10 +41,16 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
} }
AppOptions.set("defaultUrl", defaultUrl); AppOptions.set("defaultUrl", defaultUrl);
})();
let viewerApp = { initialized: false };
function initCom(app) {
viewerApp = app;
// Ensure that PDFViewerApplication.initialBookmark reflects the current hash, // Ensure that PDFViewerApplication.initialBookmark reflects the current hash,
// in case the URL rewrite above results in a different hash. // in case the URL rewrite above results in a different hash.
PDFViewerApplication.initialBookmark = location.hash.slice(1); viewerApp.initialBookmark = location.hash.slice(1);
})(); }
const ChromeCom = { const ChromeCom = {
/** /**
@ -77,10 +82,9 @@ const ChromeCom = {
* Resolves a PDF file path and attempts to detects length. * Resolves a PDF file path and attempts to detects length.
* *
* @param {string} file - Absolute URL of PDF file. * @param {string} file - Absolute URL of PDF file.
* @param {OverlayManager} overlayManager - Manager for the viewer overlays.
* @param {Function} callback - A callback with resolved URL and file length. * @param {Function} callback - A callback with resolved URL and file length.
*/ */
resolvePDFFile(file, overlayManager, callback) { resolvePDFFile(file, callback) {
// Expand drive:-URLs to filesystem URLs (Chrome OS) // Expand drive:-URLs to filesystem URLs (Chrome OS)
file = file.replace( file = file.replace(
/^drive:/i, /^drive:/i,
@ -104,13 +108,9 @@ const ChromeCom = {
// Even without this check, the file load in frames is still blocked, // Even without this check, the file load in frames is still blocked,
// but this may change in the future (https://crbug.com/550151). // but this may change in the future (https://crbug.com/550151).
if (origin && !/^file:|^chrome-extension:/.test(origin)) { if (origin && !/^file:|^chrome-extension:/.test(origin)) {
PDFViewerApplication._documentError( viewerApp._documentError(
"Blocked " + `Blocked ${origin} from loading ${file}. Refused to load ` +
origin + "a local file in a non-local page for security reasons."
" from loading " +
file +
". Refused to load a local file in a non-local page " +
"for security reasons."
); );
return; return;
} }
@ -118,7 +118,7 @@ const ChromeCom = {
if (isAllowedAccess) { if (isAllowedAccess) {
callback(file); callback(file);
} else { } else {
requestAccessToLocalFile(file, overlayManager, callback); requestAccessToLocalFile(file, viewerApp.overlayManager, callback);
} }
}); });
}); });
@ -420,7 +420,6 @@ class ExternalServices extends BaseExternalServices {
// defaultUrl is set in viewer.js // defaultUrl is set in viewer.js
ChromeCom.resolvePDFFile( ChromeCom.resolvePDFFile(
AppOptions.get("defaultUrl"), AppOptions.get("defaultUrl"),
PDFViewerApplication.overlayManager,
function (url, length, originalUrl) { function (url, length, originalUrl) {
callbacks.onOpenWithURL(url, length, originalUrl); callbacks.onOpenWithURL(url, length, originalUrl);
} }
@ -436,4 +435,4 @@ class ExternalServices extends BaseExternalServices {
} }
} }
export { ChromeCom, ExternalServices, Preferences }; export { ExternalServices, initCom, Preferences };

View File

@ -18,7 +18,6 @@ import { BaseExternalServices } from "./external_services.js";
import { BasePreferences } from "./preferences.js"; import { BasePreferences } from "./preferences.js";
import { DEFAULT_SCALE_VALUE } from "./ui_utils.js"; import { DEFAULT_SCALE_VALUE } from "./ui_utils.js";
import { L10n } from "./l10n.js"; import { L10n } from "./l10n.js";
import { PDFViewerApplication } from "./app.js";
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
throw new Error( throw new Error(
@ -26,6 +25,11 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
); );
} }
let viewerApp = { initialized: false };
function initCom(app) {
viewerApp = app;
}
class FirefoxCom { class FirefoxCom {
/** /**
* Creates an event that the extension is listening for and will * Creates an event that the extension is listening for and will
@ -167,14 +171,14 @@ class Preferences extends BasePreferences {
const findLen = "find".length; const findLen = "find".length;
const handleEvent = function ({ type, detail }) { const handleEvent = function ({ type, detail }) {
if (!PDFViewerApplication.initialized) { if (!viewerApp.initialized) {
return; return;
} }
if (type === "findbarclose") { if (type === "findbarclose") {
PDFViewerApplication.eventBus.dispatch(type, { source: window }); viewerApp.eventBus.dispatch(type, { source: window });
return; return;
} }
PDFViewerApplication.eventBus.dispatch("find", { viewerApp.eventBus.dispatch("find", {
source: window, source: window,
type: type.substring(findLen), type: type.substring(findLen),
query: detail.query, query: detail.query,
@ -194,18 +198,18 @@ class Preferences extends BasePreferences {
(function listenZoomEvents() { (function listenZoomEvents() {
const events = ["zoomin", "zoomout", "zoomreset"]; const events = ["zoomin", "zoomout", "zoomreset"];
const handleEvent = function ({ type, detail }) { const handleEvent = function ({ type, detail }) {
if (!PDFViewerApplication.initialized) { if (!viewerApp.initialized) {
return; return;
} }
// Avoid attempting to needlessly reset the zoom level *twice* in a row, // Avoid attempting to needlessly reset the zoom level *twice* in a row,
// when using the `Ctrl + 0` keyboard shortcut. // when using the `Ctrl + 0` keyboard shortcut.
if ( if (
type === "zoomreset" && type === "zoomreset" &&
PDFViewerApplication.pdfViewer.currentScaleValue === DEFAULT_SCALE_VALUE viewerApp.pdfViewer.currentScaleValue === DEFAULT_SCALE_VALUE
) { ) {
return; return;
} }
PDFViewerApplication.eventBus.dispatch(type, { source: window }); viewerApp.eventBus.dispatch(type, { source: window });
}; };
for (const event of events) { for (const event of events) {
@ -215,10 +219,10 @@ class Preferences extends BasePreferences {
(function listenSaveEvent() { (function listenSaveEvent() {
const handleEvent = function ({ type, detail }) { const handleEvent = function ({ type, detail }) {
if (!PDFViewerApplication.initialized) { if (!viewerApp.initialized) {
return; return;
} }
PDFViewerApplication.eventBus.dispatch("download", { source: window }); viewerApp.eventBus.dispatch("download", { source: window });
}; };
window.addEventListener("save", handleEvent); window.addEventListener("save", handleEvent);
@ -226,10 +230,10 @@ class Preferences extends BasePreferences {
(function listenEditingEvent() { (function listenEditingEvent() {
const handleEvent = function ({ detail }) { const handleEvent = function ({ detail }) {
if (!PDFViewerApplication.initialized) { if (!viewerApp.initialized) {
return; return;
} }
PDFViewerApplication.eventBus.dispatch("editingaction", { viewerApp.eventBus.dispatch("editingaction", {
source: window, source: window,
name: detail.name, name: detail.name,
}); });
@ -242,9 +246,9 @@ if (PDFJSDev.test("GECKOVIEW")) {
(function listenQueryEvents() { (function listenQueryEvents() {
window.addEventListener("pdf.js.query", async ({ detail: { queryId } }) => { window.addEventListener("pdf.js.query", async ({ detail: { queryId } }) => {
let result = null; let result = null;
if (queryId === "canDownloadInsteadOfPrint") { if (viewerApp.initialized && queryId === "canDownloadInsteadOfPrint") {
result = false; result = false;
const { pdfDocument, pdfViewer } = PDFViewerApplication; const { pdfDocument, pdfViewer } = viewerApp;
if (pdfDocument) { if (pdfDocument) {
try { try {
const hasUnchangedAnnotations = const hasUnchangedAnnotations =
@ -411,4 +415,4 @@ class ExternalServices extends BaseExternalServices {
} }
} }
export { DownloadManager, ExternalServices, FirefoxCom, Preferences }; export { DownloadManager, ExternalServices, initCom, Preferences };

View File

@ -25,7 +25,7 @@ if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
); );
} }
const GenericCom = {}; function initCom(app) {}
class Preferences extends BasePreferences { class Preferences extends BasePreferences {
async _writeToStorage(prefObj) { async _writeToStorage(prefObj) {
@ -47,4 +47,4 @@ class ExternalServices extends BaseExternalServices {
} }
} }
export { ExternalServices, GenericCom, Preferences }; export { ExternalServices, initCom, Preferences };

View File

@ -221,6 +221,8 @@ class IL10n {
* @interface * @interface
*/ */
class IPDFPrintServiceFactory { class IPDFPrintServiceFactory {
static initGlobals() {}
static get supportsPrinting() { static get supportsPrinting() {
return false; return false;
} }

View File

@ -15,11 +15,11 @@
import { AnnotationMode, PixelsPerInch, shadow } from "pdfjs-lib"; import { AnnotationMode, PixelsPerInch, shadow } from "pdfjs-lib";
import { getXfaHtmlForPrinting } from "./print_utils.js"; import { getXfaHtmlForPrinting } from "./print_utils.js";
import { PDFViewerApplication } from "./app.js";
let activeService = null; let activeService = null;
let dialog = null; let dialog = null;
let overlayManager = null; let overlayManager = null;
let viewerApp = { initialized: false };
// Renders the page to the canvas of the given print service, and returns // Renders the page to the canvas of the given print service, and returns
// the suggested dimensions of the output page. // the suggested dimensions of the output page.
@ -338,7 +338,7 @@ function ensureOverlay() {
); );
} }
if (!overlayPromise) { if (!overlayPromise) {
overlayManager = PDFViewerApplication.overlayManager; overlayManager = viewerApp.overlayManager;
if (!overlayManager) { if (!overlayManager) {
throw new Error("The overlay manager has not yet been initialized."); throw new Error("The overlay manager has not yet been initialized.");
} }
@ -359,6 +359,10 @@ function ensureOverlay() {
* @implements {IPDFPrintServiceFactory} * @implements {IPDFPrintServiceFactory}
*/ */
class PDFPrintServiceFactory { class PDFPrintServiceFactory {
static initGlobals(app) {
viewerApp = app;
}
static get supportsPrinting() { static get supportsPrinting() {
return shadow(this, "supportsPrinting", true); return shadow(this, "supportsPrinting", true);
} }