Bug 1715713 - Prevent showing multiple newmailalert.xhtml notification windows. r=mkmelin

This patch makes two changes:
1. If a newmailalert.xhtml window is already shown, save the folder and show a new notification only after the current notification is closed.
2. Pass new message keys to newmailalert.js. Previously, newmailalert.js receives a root folder and scans all subfolders for NEW messages, which is unnecessary and may incorrectly include old messages.

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

--HG--
extra : amend_source : fa1b6897ed980895dc24d55421cea03dc6fa3c83
This commit is contained in:
Ping Chen 2021-06-14 13:29:26 +03:00
Родитель b972d9ae1b
Коммит fa1111d359
3 изменённых файлов: 203 добавлений и 115 удалений

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

@ -47,6 +47,35 @@
return this.lastElementChild;
}
static createFolderSummaryMessage() {
let vbox = document.createXULElement("vbox");
vbox.setAttribute("class", "folderSummaryMessage");
let hbox = document.createXULElement("hbox");
hbox.setAttribute("class", "folderSummary-message-row");
let subject = document.createXULElement("label");
subject.setAttribute("class", "folderSummary-subject");
let sender = document.createXULElement("label");
sender.setAttribute("class", "folderSummary-sender");
sender.setAttribute("crop", "right");
hbox.appendChild(subject);
hbox.appendChild(sender);
let preview = document.createXULElement("description");
preview.setAttribute(
"class",
"folderSummary-message-row folderSummary-previewText"
);
preview.setAttribute("crop", "right");
vbox.appendChild(hbox);
vbox.appendChild(preview);
return vbox;
}
/**
* Check the given folder for NEW messages.
* @param {nsIMsgFolder} folder - The folder to examine.
@ -57,35 +86,6 @@
* @returns true if the folder knows about messages that should be shown.
*/
parseFolder(folder, urlListener, outAsync) {
function createFolderSummaryMessage() {
let vbox = document.createXULElement("vbox");
vbox.setAttribute("class", "folderSummaryMessage");
let hbox = document.createXULElement("hbox");
hbox.setAttribute("class", "folderSummary-message-row");
let subject = document.createXULElement("label");
subject.setAttribute("class", "folderSummary-subject");
let sender = document.createXULElement("label");
sender.setAttribute("class", "folderSummary-sender");
sender.setAttribute("crop", "right");
hbox.appendChild(subject);
hbox.appendChild(sender);
let preview = document.createXULElement("description");
preview.setAttribute(
"class",
"folderSummary-message-row folderSummary-previewText"
);
preview.setAttribute("crop", "right");
vbox.appendChild(hbox);
vbox.appendChild(preview);
return vbox;
}
// Skip servers, Trash, Junk folders and newsgroups.
if (
!folder ||
@ -190,7 +190,7 @@
i + curHdrsInPopup < this.maxMsgHdrsInPopup && i < msgKeys.length;
i++
) {
let msgBox = createFolderSummaryMessage();
let msgBox = MozFolderSummary.createFolderSummaryMessage();
let msgHdr = msgDatabase.GetMsgHdrForKey(msgKeys[i]);
msgBox.addEventListener("click", event => {
if (event.button !== 0) {
@ -244,6 +244,66 @@
}
return haveMsgsToShow;
}
/**
* Render NEW messages in a folder.
* @param {nsIMsgFolder} folder - A real folder containing new messages.
* @param {number[]} msgKeys - The keys of new messages.
*/
render(folder, msgKeys) {
let msgDatabase = folder.msgDatabase;
for (let msgKey of msgKeys) {
let msgBox = MozFolderSummary.createFolderSummaryMessage();
let msgHdr = msgDatabase.GetMsgHdrForKey(msgKey);
msgBox.addEventListener("click", event => {
if (event.button !== 0) {
return;
}
MailUtils.displayMessageInFolderTab(msgHdr);
});
if (this.showSubject) {
let msgSubject = msgHdr.mime2DecodedSubject;
const kMsgFlagHasRe = 0x0010; // MSG_FLAG_HAS_RE
if (msgHdr.flags & kMsgFlagHasRe) {
msgSubject = msgSubject ? "Re: " + msgSubject : "Re: ";
}
msgBox.querySelector(
".folderSummary-subject"
).textContent = msgSubject;
}
if (this.showSender) {
let addrs = MailServices.headerParser.parseEncodedHeader(
msgHdr.author,
msgHdr.effectiveCharset,
false
);
let folderSummarySender = msgBox.querySelector(
".folderSummary-sender"
);
// Set the label value instead of textContent to avoid wrapping.
folderSummarySender.value =
addrs.length > 0 ? addrs[0].name || addrs[0].email : "";
if (addrs.length > 1) {
let andOthersStr = this.messengerBundle.GetStringFromName(
"andOthers"
);
folderSummarySender.value += " " + andOthersStr;
}
}
if (this.showPreview) {
// Get the preview text as a UTF-8 encoded string.
msgBox.querySelector(
".folderSummary-previewText"
).textContent = decodeURIComponent(
escape(msgHdr.getStringProperty("preview") || "")
);
}
this.appendChild(msgBox);
}
}
}
customElements.define("folder-summary", MozFolderSummary);

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

@ -14,7 +14,7 @@ var NS_ALERT_TOP = 4;
var gNumNewMsgsToShowInAlert = 6;
var gOpenTime = 4000; // total time the alert should stay up once we are done animating.
var gPendingPreviewFetchRequests = 0;
var gAlertListener = null;
var gOrigin = 0; // Default value: alert from bottom right.
var gDragService = Cc["@mozilla.org/widget/dragservice;1"].getService(
Ci.nsIDragService
@ -22,70 +22,29 @@ var gDragService = Cc["@mozilla.org/widget/dragservice;1"].getService(
function prefillAlertInfo() {
// unwrap all the args....
// arguments[0] --> The nsIMsgFolder with new mail
var rootFolder = window.arguments[0];
// arguments[0] --> The real nsIMsgFolder with new mail.
// arguments[1] --> The keys of new messages.
// arguments[2] --> The nsIObserver to receive window closed event.
let [folder, newMsgKeys, listener] = window.arguments;
newMsgKeys = newMsgKeys.wrappedJSObject;
gAlertListener = listener.QueryInterface(Ci.nsIObserver);
// Generate an account label string based on the root folder.
var label = document.getElementById("alertTitle");
var totalNumNewMessages = rootFolder.getNumNewMessages(true);
var totalNumNewMessages = newMsgKeys.length;
let message = document
.getElementById("bundle_messenger")
.getString("newMailAlert_message");
label.value = PluralForm.get(totalNumNewMessages, message)
.replace("#1", rootFolder.prettyName)
.replace("#1", folder.server.rootFolder.prettyName)
.replace("#2", totalNumNewMessages);
// This is really the root folder and we have to walk through the list to
// find the real folder that has new mail in it...:(
// <folder-summary> handles rendering of new messages.
var folderSummaryInfoEl = document.getElementById("folderSummaryInfo");
folderSummaryInfoEl.maxMsgHdrsInPopup = gNumNewMsgsToShowInAlert;
for (let folder of rootFolder.descendants) {
if (folder.hasNewMessages) {
let notify =
// Any folder which is an inbox or ...
folder.getFlag(Ci.nsMsgFolderFlags.Inbox) ||
// any non-special or non-virtual folder. In other words, we don't
// notify for Drafts|Trash|SentMail|Templates|Junk|Archive|Queue or virtual.
!(
folder.flags &
(Ci.nsMsgFolderFlags.SpecialUse | Ci.nsMsgFolderFlags.Virtual)
);
if (notify) {
var asyncFetch = {};
folderSummaryInfoEl.parseFolder(
folder,
new urlListener(folder),
asyncFetch
);
if (asyncFetch.value) {
gPendingPreviewFetchRequests++;
}
}
}
}
folderSummaryInfoEl.render(folder, newMsgKeys);
}
function urlListener(aFolder) {
this.mFolder = aFolder;
}
urlListener.prototype = {
OnStartRunningUrl(aUrl) {},
OnStopRunningUrl(aUrl, aExitCode) {
let folderSummaryInfoEl = document.getElementById("folderSummaryInfo");
folderSummaryInfoEl.parseFolder(this.mFolder, null, {});
gPendingPreviewFetchRequests--;
// when we are done running all of our urls for fetching the preview text,
// start the alert.
if (!gPendingPreviewFetchRequests) {
showAlert();
}
},
};
function onAlertLoad() {
let dragSession = gDragService.getCurrentSession();
if (dragSession && dragSession.sourceNode) {
@ -106,13 +65,9 @@ function doOnAlertLoad() {
// bogus call to make sure the window is moved offscreen until we are ready for it.
resizeAlert(true);
// if we aren't waiting to fetch preview text, then go ahead and
// start showing the alert.
if (!gPendingPreviewFetchRequests) {
// Let the JS thread unwind, to give layout
// a chance to recompute the styles and widths for our alert text.
setTimeout(showAlert, 0);
}
// Let the JS thread unwind, to give layout
// a chance to recompute the styles and widths for our alert text.
setTimeout(showAlert, 0);
}
// If the user initiated the alert, show it right away, otherwise start opening the alert with
@ -186,4 +141,5 @@ function fadeOutAlert() {
function closeAlert() {
window.close();
gAlertListener.observe(null, "newmailalert-closed", "");
}

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

@ -37,6 +37,10 @@ class MailNotificationManager {
this._systemAlertAvailable = true;
this._unreadChatCount = 0;
this._unreadMailCount = 0;
// @type {Map<nsIMsgFolder, number>} - A map of folder and its last biff time.
this._folderBiffTime = new Map();
// @type {Set<nsIMsgFolder>} - A set of folders to show alert for.
this._pendingFolders = new Set();
this._logger = console.createInstance({
prefix: "mail.notification",
@ -76,22 +80,34 @@ class MailNotificationManager {
}
observe(subject, topic, data) {
if (topic == "alertclickcallback") {
// Display the associated message when an alert is clicked.
let msgHdr = Cc["@mozilla.org/messenger;1"]
.getService(Ci.nsIMessenger)
.msgHdrFromURI(data);
MailUtils.displayMessageInFolderTab(msgHdr);
} else if (topic == "unread-im-count-changed") {
this._logger.log(`Unread chat count changed to ${this._unreadChatCount}`);
this._unreadChatCount = parseInt(data, 10) || 0;
this._updateUnreadCount();
} else if (topic == "new-directed-incoming-messenger") {
this._animateDockIcon();
} else if (topic == "window-restored-from-tray") {
this._updateUnreadCount();
} else if (topic == "profile-before-change") {
this._osIntegration?.onExit();
switch (topic) {
case "alertclickcallback":
// Display the associated message when an alert is clicked.
let msgHdr = Cc["@mozilla.org/messenger;1"]
.getService(Ci.nsIMessenger)
.msgHdrFromURI(data);
MailUtils.displayMessageInFolderTab(msgHdr);
return;
case "unread-im-count-changed":
this._logger.log(
`Unread chat count changed to ${this._unreadChatCount}`
);
this._unreadChatCount = parseInt(data, 10) || 0;
this._updateUnreadCount();
return;
case "new-directed-incoming-messenger":
this._animateDockIcon();
return;
case "window-restored-from-tray":
this._updateUnreadCount();
return;
case "profile-before-change":
this._osIntegration?.onExit();
return;
case "newmailalert-closed":
// newmailalert.xhtml is closed, try to show the next queued folder.
this._customizedAlertShown = false;
this._showCustomizedAlert();
}
}
@ -125,15 +141,17 @@ class MailNotificationManager {
`OnItemIntPropertyChanged; property=${property}: ${oldValue} => ${newValue}, folder.URI=${folder.URI}`
);
if (
property == "BiffState" &&
newValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail
) {
// The folder argument is a root folder.
this._fillAlertInfo(folder);
} else if (property == "NewMailReceived") {
// The folder argument is a real folder.
this._fillAlertInfo(folder);
switch (property) {
case "BiffState":
if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail) {
// The folder argument is a root folder.
this._fillAlertInfo(folder);
}
break;
case "NewMailReceived":
// The folder argument is a real folder.
this._fillAlertInfo(folder);
break;
}
}
@ -325,13 +343,67 @@ class MailNotificationManager {
// The use_system_alert pref is false or showAlert somehow failed, use the
// customized alert window.
this._showCustomizedAlert(folder);
}
/**
* Show a customized alert window (newmailalert.xhtml), if there is already
* one showing, do not show another one, because the newer one will block the
* older one. Instead, save the folder and newMsgKeys to this._pendingFolders.
* @param {nsIMsgFolder} [folder] - The folder containing new messages.
*/
_showCustomizedAlert(folder) {
let newMsgKeys;
if (folder) {
// Show this folder or queue it.
newMsgKeys = folder.msgDatabase
.getNewList()
.slice(-folder.getNumNewMessages(false));
if (this._customizedAlertShown) {
this._pendingFolders.add(folder);
return;
}
} else {
// Get the next folder from the queue.
folder = this._pendingFolders.keys().next().value;
if (!folder) {
return;
}
let msgDb = folder.msgDatabase;
let lastBiffTime = this._folderBiffTime.get(folder) || 0;
newMsgKeys = msgDb.getNewList().filter(key => {
// It's possible that after we queued the folder, user has opened the
// folder and read some new messages. We compare the timestamp of each
// NEW message with the last biff time to make sure only real NEW
// messages are alerted.
let msgHdr = msgDb.GetMsgHdrForKey(key);
return msgHdr.dateInSeconds * 1000 > lastBiffTime;
});
this._pendingFolders.delete(folder);
}
if (newMsgKeys.length == 0) {
// No NEW message in the current folder, try the next queued folder.
this._showCustomizedAlert();
return;
}
this._folderBiffTime.set(folder, Date.now());
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
args.appendElement(folder);
args.appendElement({
wrappedJSObject: newMsgKeys,
});
args.appendElement(this);
Services.ww.openWindow(
null,
"chrome://messenger/content/newmailalert.xhtml",
"_blank",
"chrome,dialog=yes,titlebar=no,popup=yes",
folder.server.rootFolder
args
);
this._customizedAlertShown = true;
}
async _updateUnreadCount() {