diff options
Diffstat (limited to 'toolkit/mozapps/handling/content/permissionDialog.js')
-rw-r--r-- | toolkit/mozapps/handling/content/permissionDialog.js | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/toolkit/mozapps/handling/content/permissionDialog.js b/toolkit/mozapps/handling/content/permissionDialog.js new file mode 100644 index 0000000000..196d7e2411 --- /dev/null +++ b/toolkit/mozapps/handling/content/permissionDialog.js @@ -0,0 +1,232 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { EnableDelayHelper } = ChromeUtils.import( + "resource://gre/modules/SharedPromptUtils.jsm" +); + +let dialog = { + /** + * This function initializes the content of the dialog. + */ + initialize() { + let args = window.arguments[0].wrappedJSObject || window.arguments[0]; + let { + handler, + principal, + outArgs, + canPersistPermission, + preferredHandlerName, + browsingContext, + } = args; + + this._handlerInfo = handler.QueryInterface(Ci.nsIHandlerInfo); + this._principal = principal?.QueryInterface(Ci.nsIPrincipal); + this._addonPolicy = + this._principal?.addonPolicy ?? this._principal?.contentScriptAddonPolicy; + this._browsingContext = browsingContext; + this._outArgs = outArgs.QueryInterface(Ci.nsIWritablePropertyBag); + this._preferredHandlerName = preferredHandlerName; + + this._dialog = document.querySelector("dialog"); + this._checkRemember = document.getElementById("remember"); + this._checkRememberContainer = document.getElementById("rememberContainer"); + + if (!canPersistPermission) { + this._checkRememberContainer.hidden = true; + } + + let changeAppLink = document.getElementById("change-app"); + if (this._preferredHandlerName) { + changeAppLink.hidden = false; + + changeAppLink.addEventListener("click", () => this.onChangeApp()); + } + + document.addEventListener("dialogaccept", () => this.onAccept()); + document.mozSubdialogReady = this.initL10n().then(() => { + window.sizeToContent(); + }); + + this._delayHelper = new EnableDelayHelper({ + disableDialog: () => { + this._dialog.setAttribute("buttondisabledaccept", true); + }, + enableDialog: () => { + this._dialog.setAttribute("buttondisabledaccept", false); + }, + focusTarget: window, + }); + }, + + /** + * Checks whether the principal that triggered this dialog is top level + * (not embedded in a frame). + * @returns {boolean} - true if principal is top level, false otherwise. + * If the triggering principal is null this method always returns false. + */ + triggeringPrincipalIsTop() { + if (!this._principal) { + return false; + } + + let topContentPrincipal = this._browsingContext?.top.embedderElement + ?.contentPrincipal; + if (!topContentPrincipal) { + return false; + } + return this._principal.equals(topContentPrincipal); + }, + + /** + * Determines the l10n ID to use for the dialog description, depending on + * the triggering principal and the preferred application handler. + */ + get l10nDescriptionId() { + if (this._addonPolicy) { + if (this._preferredHandlerName) { + return "permission-dialog-description-extension-app"; + } + return "permission-dialog-description-extension"; + } + + if (this._principal?.schemeIs("file")) { + if (this._preferredHandlerName) { + return "permission-dialog-description-file-app"; + } + return "permission-dialog-description-file"; + } + + // We only show the website address if the request didn't come from the top + // level frame. If we can't get a host to display, fall back to the copy + // without host. + if (!this.triggeringPrincipalIsTop() && this.displayPrePath) { + if (this._preferredHandlerName) { + return "permission-dialog-description-host-app"; + } + return "permission-dialog-description-host"; + } + + if (this._preferredHandlerName) { + return "permission-dialog-description-app"; + } + + return "permission-dialog-description"; + }, + + /** + * Determines the l10n ID to use for the "remember permission" checkbox, + * depending on the triggering principal and the preferred application + * handler. + */ + get l10nCheckboxId() { + if (!this._principal) { + return null; + } + + if (this._addonPolicy) { + return "permission-dialog-remember-extension"; + } + if (this._principal.schemeIs("file")) { + return "permission-dialog-remember-file"; + } + return "permission-dialog-remember"; + }, + + /** + * Computes the prePath to show in the prompt. It's the prePath of the site + * that wants to navigate to the external protocol. + * @returns {string|null} - prePath to show, or null if we can't derive an + * exposable prePath from the triggering principal. + */ + get displayPrePath() { + if (!this._principal) { + return null; + } + + // NullPrincipals don't expose a meaningful prePath. Instead use the + // precursorPrincipal, which the NullPrincipal was derived from. + if (this._principal.isNullPrincipal) { + return this._principal.precursorPrincipal?.exposablePrePath; + } + + return this._principal?.exposablePrePath; + }, + + async initL10n() { + // The UI labels depend on whether we will show the application chooser next + // or directly open the assigned protocol handler. + + // Fluent id for dialog accept button + let idAcceptButton; + if (this._preferredHandlerName) { + idAcceptButton = "permission-dialog-btn-open-link"; + } else { + idAcceptButton = "permission-dialog-btn-choose-app"; + + let descriptionExtra = document.getElementById("description-extra"); + descriptionExtra.hidden = false; + } + + let description = document.getElementById("description"); + + document.l10n.pauseObserving(); + let pendingElements = [description]; + + let host = this.displayPrePath; + let scheme = this._handlerInfo.type; + + document.l10n.setAttributes(description, this.l10nDescriptionId, { + host, + scheme, + extension: this._addonPolicy?.name, + appName: this._preferredHandlerName, + }); + + if (!this._checkRememberContainer.hidden) { + let checkboxLabel = document.getElementById("remember-label"); + document.l10n.setAttributes(checkboxLabel, this.l10nCheckboxId, { + host, + scheme, + }); + pendingElements.push(checkboxLabel); + } + + // Set the dialog button labels. + // Ideally we would do this via attributes, however the <dialog> element + // does not support changing l10n ids on the fly. + let acceptButton = this._dialog.getButton("accept"); + let [result] = await document.l10n.formatMessages([{ id: idAcceptButton }]); + result.attributes.forEach(attr => { + if (attr.name == "label") { + acceptButton.label = attr.value; + } else { + acceptButton.accessKey = attr.value; + } + }); + + document.l10n.resumeObserving(); + + await document.l10n.translateElements(pendingElements); + return document.l10n.ready; + }, + + onAccept() { + this._outArgs.setProperty("remember", this._checkRemember.checked); + this._outArgs.setProperty("granted", true); + }, + + onChangeApp() { + this._outArgs.setProperty("resetHandlerChoice", true); + + // We can't call the dialogs accept handler here. If the accept button is + // still disabled, it will prevent closing. + this.onAccept(); + window.close(); + }, +}; + +window.addEventListener("DOMContentLoaded", () => dialog.initialize(), { + once: true, +}); |