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