Bug 355520 - Show custom account colors in the message compose From field. r=freaktechnik

Differential Revision: https://phabricator.services.mozilla.com/D213173

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alessandro Castellani 2024-06-18 22:13:52 +00:00
Родитель 9d69d1739e
Коммит 3974b3409d
5 изменённых файлов: 318 добавлений и 10 удалений

Просмотреть файл

@ -49,6 +49,7 @@ var { ExtensionParent } = ChromeUtils.importESModule(
ChromeUtils.defineESModuleGetters(this, {
BondOpenPGP: "chrome://openpgp/content/BondOpenPGP.sys.mjs",
EnigmailKeyRing: "chrome://openpgp/content/modules/keyRing.sys.mjs",
FolderTreeProperties: "resource:///modules/FolderTreeProperties.sys.mjs",
FolderUtils: "resource:///modules/FolderUtils.sys.mjs",
MailUtils: "resource:///modules/MailUtils.sys.mjs",
SelectionUtils: "resource://gre/modules/SelectionUtils.sys.mjs",
@ -379,6 +380,25 @@ const keyObserver = {
},
};
const accountObserver = {
observe(subject, topic) {
if (topic == "server-color-changed") {
// Refresh the full list to make sure we're not dealing with stale data.
const identityList = document.getElementById("msgIdentity");
if (identityList) {
const currentKey = identityList.getAttribute("identitykey");
identityList.menupopup.replaceChildren();
FillIdentityList(identityList);
identityList.selectedItem = identityList.getElementsByAttribute(
"identitykey",
currentKey
)[0];
LoadIdentity(true);
}
}
},
};
// Non translatable international shortcuts.
var SHOW_TO_KEY = "T";
var SHOW_CC_KEY = "C";
@ -4590,9 +4610,12 @@ async function ComposeStartup() {
messageEditor.addEventListener("paste", onPasteOrDrop);
messageEditor.addEventListener("drop", onPasteOrDrop);
await FolderTreeProperties.ready;
const identityList = document.getElementById("msgIdentity");
if (identityList) {
FillIdentityList(identityList);
Services.obs.addObserver(accountObserver, "server-color-changed");
}
if (!params) {
@ -5754,6 +5777,8 @@ function ComposeUnload() {
// Stop observing dictionary removals.
dictionaryRemovalObserver.removeObserver();
Services.obs.removeObserver(accountObserver, "server-color-changed");
if (gMsgCompose) {
// Notify the SendListener that Send has been aborted and Stopped
gMsgCompose.onSendNotPerformed(null, Cr.NS_ERROR_ABORT);
@ -7656,6 +7681,7 @@ function toggleAttachmentAnimation() {
function FillIdentityList(menulist) {
const accounts = FolderUtils.allAccountsSorted(true);
let hasCustomColor = false;
let accountHadSeparator = false;
let firstAccountWithIdentities = true;
for (const account of accounts) {
@ -7686,6 +7712,13 @@ function FillIdentityList(menulist) {
identity.fullAddress,
account.incomingServer.prettyName
);
const color = FolderTreeProperties.getColor(
account.incomingServer.rootFolder.URI
);
if (color) {
hasCustomColor = true;
}
item.style.setProperty("--icon-color", color ?? "");
item.setAttribute("identitykey", identity.key);
item.setAttribute("accountkey", account.key);
if (i == 0) {
@ -7702,6 +7735,8 @@ function FillIdentityList(menulist) {
}
}
menulist.classList.toggle("has-custom-color", hasCustomColor);
menulist.menupopup.appendChild(document.createXULElement("menuseparator"));
menulist.menupopup
.appendChild(document.createXULElement("menuitem"))
@ -9402,6 +9437,11 @@ function LoadIdentity(startup) {
accountKey = identityElement.selectedItem.getAttribute("accountkey");
identityElement.setAttribute("accountkey", accountKey);
identityElement.style.setProperty(
"--icon-color",
identityElement.selectedItem.style.getPropertyValue("--icon-color") ?? ""
);
// Update the addressing options only if a new account was selected.
if (prevKey != getCurrentAccountKey()) {
hideIrrelevantAddressingOptions(accountKey, prevKey);

Просмотреть файл

@ -8,11 +8,17 @@ var {
click_account_tree_row,
get_account_tree_listitem,
open_advanced_settings,
remove_account,
} = ChromeUtils.importESModule(
"resource://testing-common/mail/AccountManagerHelpers.sys.mjs"
);
var { close_compose_window, compose_window_ready } = ChromeUtils.importESModule(
"resource://testing-common/mail/ComposeHelpers.sys.mjs"
);
var { content_tab_e } = ChromeUtils.importESModule(
"resource://testing-common/mail/ContentTabHelpers.sys.mjs"
);
var { MailServices } = ChromeUtils.importESModule(
"resource:///modules/MailServices.sys.mjs"
);
@ -20,13 +26,17 @@ var { FeedUtils } = ChromeUtils.importESModule(
"resource:///modules/FeedUtils.sys.mjs"
);
var { content_tab_e } = ChromeUtils.importESModule(
"resource://testing-common/mail/ContentTabHelpers.sys.mjs"
var { promise_new_window } = ChromeUtils.importESModule(
"resource://testing-common/mail/WindowHelpers.sys.mjs"
);
var gPopAccount, gOriginalAccountCount, gFeedAccount, gLocalAccount;
var gPopAccount,
gOriginalAccountCount,
gFeedAccount,
gLocalAccount,
gComposeCtrl;
add_setup(function () {
add_setup(async function () {
// There may be pre-existing accounts from other tests.
gOriginalAccountCount = MailServices.accounts.allServers.length;
@ -53,11 +63,15 @@ add_setup(function () {
Assert.equal(
MailServices.accounts.allServers.length,
gOriginalAccountCount + 2,
"there should be one more account"
"there should be two more accounts"
);
const composePromise = promise_new_window("msgcompose");
EventUtils.synthesizeKey("n", { accelKey: true });
gComposeCtrl = await compose_window_ready(composePromise);
});
registerCleanupFunction(function () {
registerCleanupFunction(async function () {
// Remove our test account to leave the profile clean.
if (gPopAccount) {
MailServices.accounts.removeAccount(gPopAccount);
@ -72,6 +86,7 @@ registerCleanupFunction(function () {
gOriginalAccountCount,
"There should be only the original accounts left."
);
await close_compose_window(gComposeCtrl);
});
add_task(async function test_pop_account_color() {
@ -82,7 +97,8 @@ add_task(async function test_pop_account_color() {
add_task(async function test_feed_account_color() {
await open_advanced_settings(async function (tab) {
await subtest_account_color(tab, gFeedAccount);
// Feed accounts don't have an identity to send email from.
await subtest_account_color(tab, gFeedAccount, true);
});
});
@ -92,7 +108,14 @@ add_task(async function test_local_account_color() {
});
});
async function subtest_account_color(tab, account) {
/**
*
* @param {XULElement} tab - The account settings tab.
* @param {msIMsgAccount} account - The account ot test.
* @param {boolean} [skipCompose=false] - If the test should skip checking for
* the account color in the compose windows.
*/
async function subtest_account_color(tab, account, skipCompose = false) {
const customColor = "#ff0000";
const accountRow = get_account_tree_listitem(account.key, tab);
const accountTree = content_tab_e(tab, "accounttree");
@ -133,6 +156,18 @@ async function subtest_account_color(tab, account) {
"The account icon in folder pane should have a custom color"
);
if (!skipCompose) {
Assert.equal(
gComposeCtrl.document
.querySelector(
`#msgIdentityPopup menuitem[accountkey="${account.key}"]`
)
.style.getPropertyValue("--icon-color"),
customColor,
"The identity menuitem in the message compose should have a custom color"
);
}
// Switch back to account settings.
tabmail.switchToTab(1);
@ -159,6 +194,17 @@ async function subtest_account_color(tab, account) {
"The account icon in folder pane should not have a custom color"
);
if (!skipCompose) {
Assert.ok(
!gComposeCtrl.document
.querySelector(
`#msgIdentityPopup menuitem[accountkey="${account.key}"]`
)
.style.getPropertyValue("--icon-color"),
"The identity menuitem in the message compose should not have a custom color"
);
}
// Switch back to account settings.
tabmail.switchToTab(1);
}

Просмотреть файл

@ -83,6 +83,7 @@ reason = Cannot open the Format menu
[browser_forwardUTF8.js]
[browser_forwardedContent.js]
[browser_forwardedEmlActions.js]
[browser_identityColors.js]
[browser_imageDisplay.js]
[browser_imageInsertionDialog.js]
[browser_indentOutdent.js]

Просмотреть файл

@ -0,0 +1,186 @@
/* 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";
var { close_compose_window, compose_window_ready } = ChromeUtils.importESModule(
"resource://testing-common/mail/ComposeHelpers.sys.mjs"
);
var { MailServices } = ChromeUtils.importESModule(
"resource:///modules/MailServices.sys.mjs"
);
var { click_menus_in_sequence, promise_new_window } =
ChromeUtils.importESModule(
"resource://testing-common/mail/WindowHelpers.sys.mjs"
);
var { AccountManagerUtils } = ChromeUtils.importESModule(
"resource:///modules/AccountManagerUtils.sys.mjs"
);
var gPopAccount,
gOriginalAccountCount,
gLocalAccount,
gComposeCtrl,
popAMUtils,
localAMUtils;
add_setup(async function () {
// There may be pre-existing accounts from other tests.
gOriginalAccountCount = MailServices.accounts.allServers.length;
// Create a POP server.
const popServer = MailServices.accounts
.createIncomingServer("nobody", "foo.invalid", "pop3")
.QueryInterface(Ci.nsIPop3IncomingServer);
const identity = MailServices.accounts.createIdentity();
identity.email = "tinderbox@foo.invalid";
gPopAccount = MailServices.accounts.createAccount();
gPopAccount.incomingServer = popServer;
gPopAccount.addIdentity(identity);
popAMUtils = new AccountManagerUtils(gPopAccount);
popAMUtils.updateServerColor("#ff0000");
// Get the local folder account.
gLocalAccount = MailServices.accounts.findAccountForServer(
MailServices.accounts.localFoldersServer
);
localAMUtils = new AccountManagerUtils(gLocalAccount);
localAMUtils.updateServerColor("#0000ff");
Assert.equal(
MailServices.accounts.allServers.length,
gOriginalAccountCount + 1,
"there should be one more account"
);
const composePromise = promise_new_window("msgcompose");
EventUtils.synthesizeKey("n", { accelKey: true });
gComposeCtrl = await compose_window_ready(composePromise);
});
registerCleanupFunction(async function () {
// Remove our test account to leave the profile clean.
if (gPopAccount) {
MailServices.accounts.removeAccount(gPopAccount);
gPopAccount = null;
}
Assert.equal(
MailServices.accounts.allServers.length,
gOriginalAccountCount,
"There should be only the original accounts left."
);
await close_compose_window(gComposeCtrl);
});
add_task(async function test_compose_identity_colors() {
const rgb2hex = rgb =>
`#${rgb
.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)
.slice(1)
.map(n => parseInt(n, 10).toString(16).padStart(2, "0"))
.join("")}`;
const identityList = gComposeCtrl.document.getElementById("msgIdentity");
identityList.selectedIndex = 1;
Assert.ok(
identityList.classList.contains("has-custom-color"),
"The identity list should display custom account colors"
);
let pseudoStyle = getComputedStyle(identityList._labelBox, "::before");
Assert.equal(
pseudoStyle.display,
"block",
"The ::before pseudo element should be visible"
);
Assert.equal(
rgb2hex(
getComputedStyle(
identityList.menupopup.querySelector(
`menuitem[accountkey="${gPopAccount.key}"]`
),
"::before"
).backgroundColor
),
"#ff0000",
"The ::before pseudo element of the POP menulist item should use the correct color"
);
Assert.equal(
rgb2hex(
getComputedStyle(
identityList.menupopup.querySelector(
`menuitem[accountkey="${gLocalAccount.key}"]`
),
"::before"
).backgroundColor
),
"#0000ff",
"The ::before pseudo element of the POP menulist item should use the correct color"
);
// Switch to the pop account.
EventUtils.synthesizeMouseAtCenter(
identityList,
{},
identityList.ownerGlobal
);
await click_menus_in_sequence(
gComposeCtrl.document.getElementById("msgIdentityPopup"),
[{ accountkey: gPopAccount.key }]
);
pseudoStyle = getComputedStyle(identityList._labelBox, "::before");
Assert.equal(
pseudoStyle.display,
"block",
"The ::before pseudo element of the selected POP account should be visible"
);
Assert.equal(
rgb2hex(pseudoStyle.backgroundColor),
"#ff0000",
"The ::before pseudo element of the selected POP account should use the correct color"
);
// Switch to the local account.
EventUtils.synthesizeMouseAtCenter(
identityList,
{},
identityList.ownerGlobal
);
await click_menus_in_sequence(
gComposeCtrl.document.getElementById("msgIdentityPopup"),
[{ accountkey: gLocalAccount.key }]
);
pseudoStyle = getComputedStyle(identityList._labelBox, "::before");
Assert.equal(
pseudoStyle.display,
"block",
"The ::before pseudo element of the selected LOCAL account should be visible"
);
Assert.equal(
rgb2hex(pseudoStyle.backgroundColor),
"#0000ff",
"The ::before pseudo element of the selected LOCAL account should use the correct color"
);
// Clear colors.
popAMUtils.resetServerColor();
localAMUtils.resetServerColor();
Assert.ok(
!identityList.classList.contains("has-custom-color"),
"The identity list should not display custom account colors"
);
pseudoStyle = getComputedStyle(identityList._labelBox, "::before");
Assert.equal(
pseudoStyle.content,
"none",
"The ::before pseudo element of the identity list should not be visible"
);
});

Просмотреть файл

@ -869,11 +869,46 @@ toolbarbutton.formatting-button {
height: 14px;
}
#msgIdentityPopup > menuitem[selected="true"] {
#msgIdentityPopup > menuitem[selected="true"] {
background-color: var(--autocomplete-popup-highlight-background);
color: var(--autocomplete-popup-highlight-color);
}
#msgIdentity.has-custom-color {
--icon-color: var(--primary);
&[is="menulist-editable"]::part(label-box)::before {
display: block;
position: relative;
content: '';
width: 6px;
height: 6px;
border-radius: 3px;
margin-inline-end: 1px;
background-color: var(--icon-color);
}
& #msgIdentityPopup > menuitem:not(:last-child) {
--icon-color: var(--primary);
&::before {
display: block;
position: relative;
content: '';
width: 6px;
height: 6px;
border-radius: 3px;
margin-inline: -3px 4px;
background-color: var(--icon-color);
}
&[selected="true"]::before {
border-radius: 4px;
border: 1px solid var(--autocomplete-popup-highlight-color);
}
}
}
#msgSubjectContainer {
position: relative;
}