Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert the existing overlays to use <dialog> elements (issue 14698) #14710

Merged
merged 4 commits into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 10 additions & 14 deletions web/chromecom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 =
Expand Down Expand Up @@ -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);
});
}

Expand Down
104 changes: 37 additions & 67 deletions web/overlay_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down
50 changes: 28 additions & 22 deletions web/password_prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -37,8 +40,7 @@ class PasswordPrompt {
* an <iframe> or an <object>. The default value is `false`.
*/
constructor(options, overlayManager, l10n, isViewerEmbedded = false) {
this.overlayName = options.overlayName;
this.container = options.container;
this.dialog = options.dialog;
this.label = options.label;
this.input = options.input;
this.submitButton = options.submitButton;
Expand All @@ -47,9 +49,6 @@ class PasswordPrompt {
this.l10n = l10n;
this._isViewerEmbedded = isViewerEmbedded;

this.updateCallback = null;
this.reason = null;

// Attach the event listeners.
this.submitButton.addEventListener("click", this.#verify.bind(this));
this.cancelButton.addEventListener("click", this.#cancel.bind(this));
Expand All @@ -59,19 +58,16 @@ class PasswordPrompt {
}
});

this.overlayManager.register(
this.overlayName,
this.container,
this.#cancel.bind(this),
true
);
this.overlayManager.register(this.dialog, /* canForceClose = */ true);

this.dialog.addEventListener("close", this.#cancel.bind(this));
}

async open() {
await this.overlayManager.open(this.overlayName);
await this.overlayManager.open(this.dialog);

const passwordIncorrect =
this.reason === PasswordResponses.INCORRECT_PASSWORD;
this.#reason === PasswordResponses.INCORRECT_PASSWORD;

if (!this._isViewerEmbedded || passwordIncorrect) {
this.input.focus();
Expand All @@ -82,26 +78,36 @@ class PasswordPrompt {
}

async close() {
await this.overlayManager.close(this.overlayName);
this.input.value = "";
if (this.overlayManager.active === this.dialog) {
this.overlayManager.close(this.dialog);
}
}

#verify() {
const password = this.input.value;
if (password?.length > 0) {
this.close();
this.updateCallback(password);
this.#invokeCallback(password);
}
}

#cancel() {
this.#invokeCallback(new Error("PasswordPrompt cancelled."));
}

#invokeCallback(password) {
if (!this.#updateCallback) {
return; // Ensure that the callback is only invoked once.
}
this.close();
this.updateCallback(new Error("PasswordPrompt cancelled."));
this.input.value = "";

this.#updateCallback(password);
this.#updateCallback = null;
}

setUpdateCallback(updateCallback, reason) {
this.updateCallback = updateCallback;
this.reason = reason;
this.#updateCallback = updateCallback;
this.#reason = reason;
}
}

Expand Down
Loading