197 lines
5.5 KiB
JavaScript
197 lines
5.5 KiB
JavaScript
/* 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"],
|
|
});
|
|
},
|
|
|
|
hide() {
|
|
this._containerEl.style.display = "none";
|
|
},
|
|
};
|
|
|
|
document.addEventListener(
|
|
"DOMContentLoaded",
|
|
() => {
|
|
AdjustableTitle.init();
|
|
},
|
|
{ once: true }
|
|
);
|