diff --git a/package-lock.json b/package-lock.json index e14ed4a85..44e5603d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "canvas": "^2.9.1", "core-js": "^3.21.1", "cross-env": "^7.0.3", + "dialog-polyfill": "^0.5.6", "dommatrix": "^0.0.24", "es-module-shims": "1.4.7", "eslint": "^8.11.0", @@ -4350,6 +4351,12 @@ "kuler": "1.0.x" } }, + "node_modules/dialog-polyfill": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.5.6.tgz", + "integrity": "sha512-ZbVDJI9uvxPAKze6z146rmfUZjBqNEwcnFTVamQzXH+svluiV7swmVIGr7miwADgfgt1G2JQIytypM9fbyhX4w==", + "dev": true + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -21951,6 +21958,12 @@ "kuler": "1.0.x" } }, + "dialog-polyfill": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.5.6.tgz", + "integrity": "sha512-ZbVDJI9uvxPAKze6z146rmfUZjBqNEwcnFTVamQzXH+svluiV7swmVIGr7miwADgfgt1G2JQIytypM9fbyhX4w==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", diff --git a/package.json b/package.json index ea0df198b..e89f6a7be 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "canvas": "^2.9.1", "core-js": "^3.21.1", "cross-env": "^7.0.3", + "dialog-polyfill": "^0.5.6", "dommatrix": "^0.0.24", "es-module-shims": "1.4.7", "eslint": "^8.11.0", diff --git a/web/chromecom.js b/web/chromecom.js index 823251221..64b5ee042 100644 --- a/web/chromecom.js +++ b/web/chromecom.js @@ -147,7 +147,7 @@ function reloadIfRuntimeIsUnavailable() { let chromeFileAccessOverlayPromise; function requestAccessToLocalFile(fileUrl, overlayManager, callback) { - let onCloseOverlay = null; + const dialog = document.getElementById("chromeFileAccessDialog"); if (top !== window) { // When the extension reloads after receiving new permissions, the pages // have to be reloaded to restore the extension runtime. Auto-reload @@ -157,20 +157,16 @@ function requestAccessToLocalFile(fileUrl, overlayManager, callback) { // for detecting unload of the top-level frame. Should this ever change // (crbug.com/511670), then the user can just reload the tab. window.addEventListener("focus", reloadIfRuntimeIsUnavailable); - onCloseOverlay = function () { + dialog.addEventListener("close", function () { window.removeEventListener("focus", reloadIfRuntimeIsUnavailable); reloadIfRuntimeIsUnavailable(); - overlayManager.close("chromeFileAccessOverlay"); - }; - } - if (!chromeFileAccessOverlayPromise) { - chromeFileAccessOverlayPromise = overlayManager.register( - "chromeFileAccessOverlay", - document.getElementById("chromeFileAccessOverlay"), - onCloseOverlay, - true - ); + }); } + chromeFileAccessOverlayPromise ||= overlayManager.register( + dialog, + /* canForceClose = */ true + ); + chromeFileAccessOverlayPromise.then(function () { const iconPath = chrome.runtime.getManifest().icons[48]; document.getElementById("chrome-pdfjs-logo-bg").style.backgroundImage = @@ -229,11 +225,11 @@ function requestAccessToLocalFile(fileUrl, overlayManager, callback) { originalUrl = "file:///fakepath/to/" + encodeURIComponent(file.name); } callback(URL.createObjectURL(file), file.size, originalUrl); - overlayManager.close("chromeFileAccessOverlay"); + overlayManager.close(dialog); } }; - overlayManager.open("chromeFileAccessOverlay"); + overlayManager.open(dialog); }); } diff --git a/web/overlay_manager.js b/web/overlay_manager.js index b77f4a61a..e4d7378e1 100644 --- a/web/overlay_manager.js +++ b/web/overlay_manager.js @@ -14,123 +14,93 @@ */ class OverlayManager { - #overlays = Object.create(null); + #overlays = new WeakMap(); #active = null; - #keyDownBound = null; - get active() { return this.#active; } /** - * @param {string} name - The name of the overlay that is registered. - * @param {HTMLDivElement} element - The overlay's DOM element. - * @param {function} [callerCloseMethod] - The method that, if present, calls - * `OverlayManager.close` from the object registering the - * overlay. Access to this method is necessary in order to - * run cleanup code when e.g. the overlay is force closed. - * The default is `null`. + * @param {HTMLDialogElement} dialog - The overlay's DOM element. * @param {boolean} [canForceClose] - Indicates if opening the overlay closes * an active overlay. The default is `false`. * @returns {Promise} A promise that is resolved when the overlay has been * registered. */ - async register( - name, - element, - callerCloseMethod = null, - canForceClose = false - ) { - let container; - if (!name || !element || !(container = element.parentNode)) { + async register(dialog, canForceClose = false) { + if (typeof dialog !== "object") { throw new Error("Not enough parameters."); - } else if (this.#overlays[name]) { + } else if (this.#overlays.has(dialog)) { throw new Error("The overlay is already registered."); } - this.#overlays[name] = { - element, - container, - callerCloseMethod, - canForceClose, - }; + this.#overlays.set(dialog, { canForceClose }); + + if ( + typeof PDFJSDev !== "undefined" && + PDFJSDev.test("GENERIC && !SKIP_BABEL") && + !dialog.showModal + ) { + const dialogPolyfill = require("dialog-polyfill/dist/dialog-polyfill.js"); + dialogPolyfill.registerDialog(dialog); + } + + dialog.addEventListener("cancel", evt => { + this.#active = null; + }); } /** - * @param {string} name - The name of the overlay that is unregistered. + * @param {HTMLDialogElement} dialog - The overlay's DOM element. * @returns {Promise} A promise that is resolved when the overlay has been * unregistered. */ - async unregister(name) { - if (!this.#overlays[name]) { + async unregister(dialog) { + if (!this.#overlays.has(dialog)) { throw new Error("The overlay does not exist."); - } else if (this.#active === name) { + } else if (this.#active === dialog) { throw new Error("The overlay cannot be removed while it is active."); } - delete this.#overlays[name]; + this.#overlays.delete(dialog); } /** - * @param {string} name - The name of the overlay that should be opened. + * @param {HTMLDialogElement} dialog - The overlay's DOM element. * @returns {Promise} A promise that is resolved when the overlay has been * opened. */ - async open(name) { - if (!this.#overlays[name]) { + async open(dialog) { + if (!this.#overlays.has(dialog)) { throw new Error("The overlay does not exist."); } else if (this.#active) { - if (this.#active === name) { + if (this.#active === dialog) { throw new Error("The overlay is already active."); - } else if (this.#overlays[name].canForceClose) { - this.#closeThroughCaller(); + } else if (this.#overlays.get(dialog).canForceClose) { + await this.close(); } else { throw new Error("Another overlay is currently active."); } } - this.#active = name; - this.#overlays[this.#active].element.classList.remove("hidden"); - this.#overlays[this.#active].container.classList.remove("hidden"); - - this.#keyDownBound = this.#keyDown.bind(this); - window.addEventListener("keydown", this.#keyDownBound); + this.#active = dialog; + dialog.showModal(); } /** - * @param {string} name - The name of the overlay that should be closed. + * @param {HTMLDialogElement} dialog - The overlay's DOM element. * @returns {Promise} A promise that is resolved when the overlay has been * closed. */ - async close(name) { - if (!this.#overlays[name]) { + async close(dialog = this.#active) { + if (!this.#overlays.has(dialog)) { throw new Error("The overlay does not exist."); } else if (!this.#active) { throw new Error("The overlay is currently not active."); - } else if (this.#active !== name) { + } else if (this.#active !== dialog) { throw new Error("Another overlay is currently active."); } - this.#overlays[this.#active].container.classList.add("hidden"); - this.#overlays[this.#active].element.classList.add("hidden"); + dialog.close(); this.#active = null; - - window.removeEventListener("keydown", this.#keyDownBound); - this.#keyDownBound = null; - } - - #keyDown(evt) { - if (this.#active && evt.keyCode === /* Esc = */ 27) { - this.#closeThroughCaller(); - evt.preventDefault(); - } - } - - #closeThroughCaller() { - if (this.#overlays[this.#active].callerCloseMethod) { - this.#overlays[this.#active].callerCloseMethod(); - } - if (this.#active) { - this.close(this.#active); - } } } diff --git a/web/password_prompt.js b/web/password_prompt.js index 6631783c7..2207b1a9d 100644 --- a/web/password_prompt.js +++ b/web/password_prompt.js @@ -17,8 +17,7 @@ import { PasswordResponses } from "pdfjs-lib"; /** * @typedef {Object} PasswordPromptOptions - * @property {string} overlayName - Name of the overlay for the overlay manager. - * @property {HTMLDivElement} container - Div container for the overlay. + * @property {HTMLDialogElement} dialog - The overlay's DOM element. * @property {HTMLParagraphElement} label - Label containing instructions for * entering the password. * @property {HTMLInputElement} input - Input field for entering the password. @@ -29,6 +28,10 @@ import { PasswordResponses } from "pdfjs-lib"; */ class PasswordPrompt { + #updateCallback = null; + + #reason = null; + /** * @param {PasswordPromptOptions} options * @param {OverlayManager} overlayManager - Manager for the viewer overlays. @@ -37,8 +40,7 @@ class PasswordPrompt { * an