Bug 1693008 - add an iconified title to commonDialog using a reusable script, r=mtigley

Differential Revision: https://phabricator.services.mozilla.com/D108656
This commit is contained in:
Gijs Kruitbosch 2021-03-23 15:02:06 +00:00
parent 4c0ed07ed9
commit 4c87898068
7 changed files with 182 additions and 11 deletions

View File

@ -0,0 +1,151 @@
/* 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.import(
"resource://gre/modules/SharedPromptUtils.jsm"
);
// We expect our consumer to provide Services.jsm.
/* global Services */
const AdjustableTitle = {
_cssSnippet: `
#titleContainer {
display: flex;
flex-direction: row;
margin-inline: 4px;
--icon-size: 20px; /* 16px icon + 4px spacing. */
padding-inline-start: var(--icon-size);
/* Ensure we don't exceed the bounds of the dialog minus our own padding: */
max-width: calc(100vw - 32px - var(--icon-size));
overflow: hidden;
justify-content: end;
position: relative;
min-height: max(16px, 1.5em);
}
#titleContainer[noicon] {
--icon-size: 0;
}
#titleContainer[noicon] > .titleIcon {
display: none;
}
.titleIcon {
/* We put the icon in its own element rather than on .titleContainer, because
* it needs to overlap the text when the text overflows.
*/
position: absolute;
/* center vertically: */
top: max(0px, calc(0.75em - 8px));
left: 0;
width: 16px;
padding-right: 4px;
height: 16px;
background-image: var(--icon-url, url("chrome://global/skin/icons/defaultFavicon.svg"));
background-size: 16px 16px;
background-repeat: no-repeat;
background-color: var(--in-content-page-background);
-moz-context-properties: fill;
fill: currentColor;
}
#titleText {
font-weight: 600;
flex: 1 0 auto; /* Grow but do not shrink. */
white-space: nowrap;
}
#titleContainer[overflown] {
mask-image: linear-gradient(to right, black, black 20px, transparent 20px, black 120px);
mask-repeat: no-repeat;
}
/* hide the old title */
#infoTitle {
display: none;
}
`,
_insertMarkup() {
let iconEl = document.createElement("div");
iconEl.className = "titleIcon";
this._titleEl = document.createElement("span");
this._titleEl.id = "titleText";
this._containerEl = document.createElement("div");
this._containerEl.id = "titleContainer";
this._containerEl.className = "dialogRow titleContainer";
this._containerEl.append(iconEl, this._titleEl);
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 = await window.promiseDocumentFlushed(() => {
return (
this._containerEl.getBoundingClientRect().width -
this._titleEl.getBoundingClientRect().width -
this._titleEl.previousElementSibling.getBoundingClientRect().width <
0
);
});
this._containerEl.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;
} else {
document.l10n.setAttributes(this._titleEl, title.l10nId);
}
if (!document.documentElement.hasAttribute("neediconheader")) {
this._containerEl.setAttribute("noicon", "true");
} else {
this._overflowHandler();
}
},
init() {
// Only run this if we're embedded and proton modals are enabled.
if (!window.docShell.chromeEventHandler || !PromptUtils.protonModals) {
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 }
);

View File

@ -49,7 +49,7 @@
min-height: 0;
}
@supports not -moz-bool-pref("browser.proton.enabled") {
@supports not -moz-bool-pref("browser.proton.modals.enabled") {
.sizeDetermined,
.sizeDetermined .dialogRow,
.sizeDetermined #infoContainer {
@ -76,7 +76,7 @@ dialog[subdialog] #infoContainer {
text-align: match-parent;
}
@supports -moz-bool-pref("browser.proton.enabled") {
@supports -moz-bool-pref("browser.proton.modals.enabled") {
/* use flexbox instead of grid: */
dialog[subdialog],
dialog[subdialog] #dialogGrid,
@ -119,8 +119,7 @@ dialog[subdialog] {
}
/* Add vertical spaces between rows: */
dialog[subdialog] .dialogRow,
dialog[subdialog] #infoTitle {
dialog[subdialog] .dialogRow {
margin-block: 0 20px;
}

View File

@ -6,6 +6,8 @@ const { CommonDialog } = ChromeUtils.import(
"resource://gre/modules/CommonDialog.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var propBag, args, Dialog;
function commonDialogOnLoad() {
@ -20,6 +22,16 @@ function commonDialogOnLoad() {
let dialog = document.getElementById("commonDialog");
let needIconifiedHeader =
args.modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT ||
["promptUserAndPass", "promptPassword"].includes(args.promptType);
let root = document.documentElement;
if (needIconifiedHeader) {
root.setAttribute("neediconheader", "true");
}
let title = { raw: args.title };
root.setAttribute("headertitle", JSON.stringify(title));
let ui = {
prompt: window,
loginContainer: document.getElementById("loginContainer"),

View File

@ -15,10 +15,12 @@
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
aria-describedby="infoBody"
headerparent="dialogGrid"
onunload="commonDialogOnUnload();">
<dialog id="commonDialog"
buttonpack="center">
<script src="chrome://global/content/adjustableTitle.js"/>
<script src="chrome://global/content/commonDialog.js"/>
<script src="chrome://global/content/globalOverlay.js"/>
<script src="chrome://global/content/editMenuOverlay.js"/>

View File

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
content/global/adjustableTitle.js (content/adjustableTitle.js)
content/global/commonDialog.js (content/commonDialog.js)
content/global/commonDialog.xhtml (content/commonDialog.xhtml)
content/global/commonDialog.css (content/commonDialog.css)

View File

@ -40,6 +40,8 @@ CommonDialog.prototype = {
* null for TabModalPrompts.
*/
async onLoad(commonDialogEl = null) {
let isEmbedded = !!commonDialogEl?.ownerGlobal.docShell.chromeEventHandler;
switch (this.args.promptType) {
case "alert":
case "alertCheck":
@ -132,11 +134,7 @@ CommonDialog.prototype = {
// macOS (where there is no titlebar) or if the prompt is a common dialog document
// and has been embedded (has a chromeEventHandler).
infoTitle.hidden =
isOldContentPrompt ||
!(
AppConstants.platform === "macosx" ||
commonDialogEl?.ownerGlobal.docShell.chromeEventHandler
);
isOldContentPrompt || !(AppConstants.platform === "macosx" || isEmbedded);
if (commonDialogEl) {
commonDialogEl.ownerDocument.title = title;
@ -212,8 +210,6 @@ CommonDialog.prototype = {
button.setAttribute("default", "true");
}
let isEmbedded =
commonDialogEl && this.ui.prompt.docShell.chromeEventHandler;
if (!isEmbedded && !this.ui.promptContainer?.hidden) {
// Set default focus and select textbox contents if applicable. If we're
// embedded SubDialogManager will call setDefaultFocus for us.

View File

@ -5,6 +5,9 @@
var EXPORTED_SYMBOLS = ["PromptUtils", "EnableDelayHelper"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
var PromptUtils = {
// Fire a dialog open/close event. Used by tabbrowser to focus the
@ -50,6 +53,13 @@ var PromptUtils = {
},
};
XPCOMUtils.defineLazyPreferenceGetter(
PromptUtils,
"protonModals",
"browser.proton.modals.enabled",
false
);
/**
* This helper handles the enabling/disabling of dialogs that might
* be subject to fast-clicking attacks. It handles the initial delayed