mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
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:
parent
4c0ed07ed9
commit
4c87898068
151
toolkit/components/prompts/content/adjustableTitle.js
Normal file
151
toolkit/components/prompts/content/adjustableTitle.js
Normal 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 }
|
||||
);
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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"/>
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user