/* 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 } );