diff options
Diffstat (limited to 'toolkit/components/prompts/content/adjustableTitle.js')
-rw-r--r-- | toolkit/components/prompts/content/adjustableTitle.js | 193 |
1 files changed, 193 insertions, 0 deletions
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 } +); |