From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- .../components/prompts/content/adjustableTitle.js | 193 +++++++++++++ .../components/prompts/content/commonDialog.css | 124 +++++++++ toolkit/components/prompts/content/commonDialog.js | 148 ++++++++++ .../components/prompts/content/commonDialog.xhtml | 103 +++++++ toolkit/components/prompts/content/selectDialog.js | 83 ++++++ .../components/prompts/content/selectDialog.xhtml | 21 ++ toolkit/components/prompts/content/tabprompts.css | 119 ++++++++ .../components/prompts/content/tabprompts.sys.mjs | 298 +++++++++++++++++++++ 8 files changed, 1089 insertions(+) create mode 100644 toolkit/components/prompts/content/adjustableTitle.js create mode 100644 toolkit/components/prompts/content/commonDialog.css create mode 100644 toolkit/components/prompts/content/commonDialog.js create mode 100644 toolkit/components/prompts/content/commonDialog.xhtml create mode 100644 toolkit/components/prompts/content/selectDialog.js create mode 100644 toolkit/components/prompts/content/selectDialog.xhtml create mode 100644 toolkit/components/prompts/content/tabprompts.css create mode 100644 toolkit/components/prompts/content/tabprompts.sys.mjs (limited to 'toolkit/components/prompts/content') diff --git a/toolkit/components/prompts/content/adjustableTitle.js b/toolkit/components/prompts/content/adjustableTitle.js new file mode 100644 index 0000000000..bd9afd909a --- /dev/null +++ b/toolkit/components/prompts/content/adjustableTitle.js @@ -0,0 +1,193 @@ +/* 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/. */ + +"use strict"; + +let { PromptUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromptUtils.sys.mjs" +); + +const AdjustableTitle = { + _cssSnippet: ` + #titleContainer { + /* This gets display: flex by virtue of being a row in a subdialog, from + * commonDialog.css . */ + flex-shrink: 0; + + flex-direction: row; + align-items: baseline; + + margin-inline: 4px; + /* Ensure we don't exceed the bounds of the dialog: */ + max-width: calc(100vw - 32px); + + --icon-size: 16px; + } + + #titleContainer[noicon] > .titleIcon { + display: none; + } + + .titleIcon { + width: var(--icon-size); + height: var(--icon-size); + padding-inline-end: 4px; + flex-shrink: 0; + + background-image: var(--icon-url, url("chrome://global/skin/icons/defaultFavicon.svg")); + background-size: 16px 16px; + background-origin: content-box; + background-repeat: no-repeat; + background-color: var(--in-content-page-background); + -moz-context-properties: fill; + fill: currentColor; + } + + #titleCropper:not([nomaskfade]) { + display: inline-flex; + } + + #titleCropper { + overflow: hidden; + + justify-content: right; + mask-repeat: no-repeat; + /* go from left to right with the mask: */ + --mask-dir: right; + } + + #titleContainer:not([noicon]) > #titleCropper { + /* Align the icon and text: */ + translate: 0 calc(-1px - max(.6 * var(--icon-size) - .6em, 0px)); + } + + #titleCropper[rtlorigin] { + justify-content: left; + /* go from right to left with the mask: */ + --mask-dir: left; + } + + + #titleCropper:not([nomaskfade]) #titleText { + display: inline-flex; + white-space: nowrap; + } + + #titleText { + font-weight: 600; + flex: 1 0 auto; /* Grow but do not shrink. */ + unicode-bidi: plaintext; /* Ensure we align RTL text correctly. */ + text-align: match-parent; + } + + #titleCropper[overflown] { + mask-image: linear-gradient(to var(--mask-dir), transparent, black 100px); + } + + /* hide the old title */ + #infoTitle { + display: none; + } + `, + + _insertMarkup() { + let iconEl = document.createElement("span"); + iconEl.className = "titleIcon"; + this._titleCropEl = document.createElement("span"); + this._titleCropEl.id = "titleCropper"; + this._titleEl = document.createElement("span"); + this._titleEl.id = "titleText"; + this._containerEl = document.createElement("div"); + this._containerEl.id = "titleContainer"; + this._containerEl.className = "dialogRow titleContainer"; + this._titleCropEl.append(this._titleEl); + this._containerEl.append(iconEl, this._titleCropEl); + let targetID = document.documentElement.getAttribute("headerparent"); + document.getElementById(targetID).prepend(this._containerEl); + let styleEl = document.createElement("style"); + styleEl.textContent = this._cssSnippet; + document.documentElement.prepend(styleEl); + }, + + _overflowHandler() { + requestAnimationFrame(async () => { + let isOverflown; + try { + isOverflown = await window.promiseDocumentFlushed(() => { + return ( + this._titleCropEl.getBoundingClientRect().width < + this._titleEl.getBoundingClientRect().width + ); + }); + } catch (ex) { + // In automated tests, this can fail with a DOM exception if + // the window has closed by the time layout tries to call us. + // In this case, just bail, and only log any other errors: + if ( + !DOMException.isInstance(ex) || + ex.name != "NoModificationAllowedError" + ) { + console.error(ex); + } + return; + } + this._titleCropEl.toggleAttribute("overflown", isOverflown); + if (isOverflown) { + this._titleEl.setAttribute("title", this._titleEl.textContent); + } else { + this._titleEl.removeAttribute("title"); + } + }); + }, + + _updateTitle(title) { + title = JSON.parse(title); + if (title.raw) { + this._titleEl.textContent = title.raw; + let { DIRECTION_RTL } = window.windowUtils; + this._titleCropEl.toggleAttribute( + "rtlorigin", + window.windowUtils.getDirectionFromText(title.raw) == DIRECTION_RTL + ); + } else { + document.l10n.setAttributes(this._titleEl, title.l10nId); + } + + if (!document.documentElement.hasAttribute("neediconheader")) { + this._containerEl.setAttribute("noicon", "true"); + } else if (title.shouldUseMaskFade) { + this._overflowHandler(); + } else { + this._titleCropEl.toggleAttribute("nomaskfade", true); + } + }, + + init() { + // Only run this if we're embedded and proton modals are enabled. + if (!window.docShell.chromeEventHandler) { + return; + } + + this._insertMarkup(); + let title = document.documentElement.getAttribute("headertitle"); + if (title) { + this._updateTitle(title); + } + this._mutObs = new MutationObserver(() => { + this._updateTitle(document.documentElement.getAttribute("headertitle")); + }); + this._mutObs.observe(document.documentElement, { + attributes: true, + attributeFilter: ["headertitle"], + }); + }, +}; + +document.addEventListener( + "DOMContentLoaded", + () => { + AdjustableTitle.init(); + }, + { once: true } +); diff --git a/toolkit/components/prompts/content/commonDialog.css b/toolkit/components/prompts/content/commonDialog.css new file mode 100644 index 0000000000..3521af13c6 --- /dev/null +++ b/toolkit/components/prompts/content/commonDialog.css @@ -0,0 +1,124 @@ +/* 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/. */ + +:root { + min-width: 29em; +} + +dialog[insecureauth] { + --icon-url: url("chrome://global/skin/icons/security-broken.svg"); +} + +#dialogGrid { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + max-height: 100%; +} + +.dialogRow:not([hidden]) { + display: contents; +} + +#iconContainer { + align-self: start; +} + +#infoContainer { + max-width: 45em; +} + +#infoTitle { + margin-bottom: 1em; +} + +#infoBody { + -moz-user-focus: normal; + user-select: text; + cursor: text !important; + white-space: pre-wrap; + word-break: break-word; + unicode-bidi: plaintext; + overflow-y: auto; +} + +.sizeDetermined, +.sizeDetermined::part(content-box), +.sizeDetermined #infoBody, +.sizeDetermined #infoRow, +.sizeDetermined #infoContainer { + /* Allow stuff to shrink */ + min-height: 0; +} + +.sizeDetermined #infoRow { + /* Make the info row take all the available space, potentially shrinking, + * since it's what contains the infoBody, which is scrollable */ + flex: 1; +} + +#loginLabel, #password1Label { + text-align: start; +} + +#loginTextbox, +#password1Textbox { + text-align: match-parent; +} + +/* use flexbox instead of grid: */ +dialog[subdialog], +dialog[subdialog] #dialogGrid, +dialog[subdialog] #infoContainer, +dialog[subdialog] .dialogRow:not([hidden]) { + display: flex; + flex-direction: column; + align-items: stretch; +} + +dialog[subdialog] #iconContainer { + display: none; +} + +/* Fix padding/spacing */ +dialog[subdialog] { + --grid-padding: 16px; + /* All the inner items should have 4px inline margin, leading to 1.16em spacing + * between the dialog and its contents, and 8px horizontal spacing between items. */ + padding: var(--grid-padding) calc(var(--grid-padding) - 4px); +} + +/* Use an ID selector for the dialog to win on specificity... */ +#commonDialog[subdialog] description, +#commonDialog[subdialog] checkbox { + margin: 0 4px; +} + +#commonDialog[subdialog] label { + margin: 4px; /* Labels for input boxes should get block as well as the regular inline margin. */ +} + +#commonDialog[subdialog] .checkbox-label { + /* The checkbox already has the horizontal margin, so override the rule + * above. */ + margin: 0; +} + +#commonDialog[subdialog] input { + margin: 0 4px 4px; +} + +/* Add vertical spaces between rows: */ +dialog[subdialog] .dialogRow { + margin-block: 0 var(--grid-padding); +} + +/* Fix spacing in the `prompt()` case: */ +dialog[subdialog] #loginLabel[value=""] { + display: none; +} + +dialog[subdialog][windowtype="prompt:prompt"] #infoRow { + margin-bottom: 4px; +} diff --git a/toolkit/components/prompts/content/commonDialog.js b/toolkit/components/prompts/content/commonDialog.js new file mode 100644 index 0000000000..a18b7cbd6c --- /dev/null +++ b/toolkit/components/prompts/content/commonDialog.js @@ -0,0 +1,148 @@ +/* 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 { CommonDialog } = ChromeUtils.importESModule( + "resource://gre/modules/CommonDialog.sys.mjs" +); + +// imported by adjustableTitle.js loaded in the same context: +/* globals PromptUtils */ + +var propBag, args, Dialog; + +// Inherit color scheme overrides from parent window. This is to inherit the +// color scheme of dark themed PBM windows. +{ + let openerColorSchemeOverride = + window.opener?.browsingContext?.top.prefersColorSchemeOverride; + if ( + openerColorSchemeOverride && + window.browsingContext == window.browsingContext.top + ) { + window.browsingContext.prefersColorSchemeOverride = + openerColorSchemeOverride; + } +} + +function commonDialogOnLoad() { + propBag = window.arguments[0] + .QueryInterface(Ci.nsIWritablePropertyBag2) + .QueryInterface(Ci.nsIWritablePropertyBag); + // Convert to a JS object + args = {}; + for (let prop of propBag.enumerator) { + args[prop.name] = prop.value; + } + + let dialog = document.getElementById("commonDialog"); + + let needIconifiedHeader = + args.modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT || + ["promptUserAndPass", "promptPassword"].includes(args.promptType) || + args.headerIconURL; + let root = document.documentElement; + if (needIconifiedHeader) { + root.setAttribute("neediconheader", "true"); + } + let title = { raw: args.title }; + let { promptPrincipal } = args; + if (promptPrincipal) { + if (promptPrincipal.isNullPrincipal) { + title = { l10nId: "common-dialog-title-null" }; + } else if (promptPrincipal.isSystemPrincipal) { + title = { l10nId: "common-dialog-title-system" }; + root.style.setProperty( + "--icon-url", + "url('chrome://branding/content/icon32.png')" + ); + } else if (promptPrincipal.addonPolicy) { + title.raw = promptPrincipal.addonPolicy.name; + } else if (promptPrincipal.isContentPrincipal) { + try { + title.raw = promptPrincipal.URI.displayHostPort; + } catch (ex) { + // hostPort getter can throw, e.g. for about URIs. + title.raw = promptPrincipal.originNoSuffix; + } + // hostPort can be empty for file URIs. + if (!title.raw) { + title.raw = promptPrincipal.prePath; + } + } else { + title = { l10nId: "common-dialog-title-unknown" }; + } + } else if (args.authOrigin) { + title = { raw: args.authOrigin }; + } + if (args.headerIconURL) { + root.style.setProperty("--icon-url", `url('${args.headerIconURL}')`); + } + // Fade and crop potentially long raw titles, e.g., origins and hostnames. + title.shouldUseMaskFade = title.raw && (args.authOrigin || promptPrincipal); + root.setAttribute("headertitle", JSON.stringify(title)); + if (args.isInsecureAuth) { + dialog.setAttribute("insecureauth", "true"); + } + + let ui = { + prompt: window, + loginContainer: document.getElementById("loginContainer"), + loginTextbox: document.getElementById("loginTextbox"), + loginLabel: document.getElementById("loginLabel"), + password1Container: document.getElementById("password1Container"), + password1Textbox: document.getElementById("password1Textbox"), + password1Label: document.getElementById("password1Label"), + infoRow: document.getElementById("infoRow"), + infoBody: document.getElementById("infoBody"), + infoTitle: document.getElementById("infoTitle"), + infoIcon: document.getElementById("infoIcon"), + checkbox: document.getElementById("checkbox"), + checkboxContainer: document.getElementById("checkboxContainer"), + button3: dialog.getButton("extra2"), + button2: dialog.getButton("extra1"), + button1: dialog.getButton("cancel"), + button0: dialog.getButton("accept"), + focusTarget: window, + }; + + Dialog = new CommonDialog(args, ui); + window.addEventListener("dialogclosing", function (aEvent) { + if (aEvent.detail?.abort) { + Dialog.abortPrompt(); + } + }); + document.addEventListener("dialogaccept", function () { + Dialog.onButton0(); + }); + document.addEventListener("dialogcancel", function () { + Dialog.onButton1(); + }); + document.addEventListener("dialogextra1", function () { + Dialog.onButton2(); + window.close(); + }); + document.addEventListener("dialogextra2", function () { + Dialog.onButton3(); + window.close(); + }); + document.subDialogSetDefaultFocus = isInitialFocus => + Dialog.setDefaultFocus(isInitialFocus); + Dialog.onLoad(dialog); + + // resize the window to the content + window.sizeToContent(); + + // If the icon hasn't loaded yet, size the window to the content again when + // it does, as its layout can change. + ui.infoIcon.addEventListener("load", () => window.sizeToContent()); + + window.getAttention(); +} + +function commonDialogOnUnload() { + // Convert args back into property bag + for (let propName in args) { + propBag.setProperty(propName, args[propName]); + } +} diff --git a/toolkit/components/prompts/content/commonDialog.xhtml b/toolkit/components/prompts/content/commonDialog.xhtml new file mode 100644 index 0000000000..02e0749a9e --- /dev/null +++ b/toolkit/components/prompts/content/commonDialog.xhtml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
diff --git a/toolkit/components/prompts/content/selectDialog.js b/toolkit/components/prompts/content/selectDialog.js new file mode 100644 index 0000000000..86809bc879 --- /dev/null +++ b/toolkit/components/prompts/content/selectDialog.js @@ -0,0 +1,83 @@ +/* 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/. */ + +// Defined in dialog.xml. +/* globals centerWindowOnScreen:false, moveToAlertPosition:false */ + +var propBag, listBox, args; + +function onDCL() { + propBag = window.arguments[0] + .QueryInterface(Ci.nsIWritablePropertyBag2) + .QueryInterface(Ci.nsIWritablePropertyBag); + + // Convert to a JS object + let args = {}; + for (let prop of propBag.enumerator) { + args[prop.name] = prop.value; + } + + let promptType = propBag.getProperty("promptType"); + if (promptType != "select") { + console.error("selectDialog opened for unknown type: ", promptType); + window.close(); + } + + // Default to canceled. + propBag.setProperty("ok", false); + + document.title = propBag.getProperty("title"); + + let text = propBag.getProperty("text"); + document.getElementById("info.txt").setAttribute("value", text); + + let items = propBag.getProperty("list"); + listBox = document.getElementById("list"); + + for (let i = 0; i < items.length; i++) { + let str = items[i]; + if (str == "") { + str = "<>"; + } + listBox.appendItem(str); + listBox.getItemAtIndex(i).addEventListener("dblclick", dialogDoubleClick); + } + listBox.selectedIndex = 0; +} + +function onLoad() { + listBox.focus(); + + document.addEventListener("dialogaccept", dialogOK); + // resize the window to the content + window.sizeToContent(); + + // Move to the right location + moveToAlertPosition(); + centerWindowOnScreen(); + + // play sound + try { + if (!args.openedWithTabDialog) { + Cc["@mozilla.org/sound;1"] + .getService(Ci.nsISound) + .playEventSound(Ci.nsISound.EVENT_SELECT_DIALOG_OPEN); + } + } catch (e) {} + + Services.obs.notifyObservers(window, "select-dialog-loaded"); +} + +function dialogOK() { + propBag.setProperty("selected", listBox.selectedIndex); + propBag.setProperty("ok", true); +} + +function dialogDoubleClick() { + dialogOK(); + window.close(); +} + +document.addEventListener("DOMContentLoaded", onDCL); +window.addEventListener("load", onLoad, { once: true }); diff --git a/toolkit/components/prompts/content/selectDialog.xhtml b/toolkit/components/prompts/content/selectDialog.xhtml new file mode 100644 index 0000000000..27963ebd57 --- /dev/null +++ b/toolkit/components/prompts/content/selectDialog.xhtml @@ -0,0 +1,21 @@ + + + + + + + + + +