Bug 1855758 - Implement better progress notifications from the parser to the front end. r=BenC,mkmelin

So that we can trigger actions as soon as the message body is ready, we need to know when the
parser has finished outputting it.

Unfortunately my original plan of using web progress notifications is inadequate as it's not
possible to pass useful information with the notifications, so this patch implements a specialist
notification. (Thanks to nsBrowserStatusFilter for this, it discards almost all the data it
receives.) The "mark message as read" action is then connected to the notification.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Geoff Lankow 2023-10-19 22:33:42 +00:00
Родитель 4ae1e23f28
Коммит 83e27447ce
6 изменённых файлов: 98 добавлений и 74 удалений

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

@ -454,7 +454,7 @@ async function OnLoadMsgHeaderPane() {
getMessagePaneBrowser().addProgressListener( getMessagePaneBrowser().addProgressListener(
messageProgressListener, messageProgressListener,
Ci.nsIWebProgress.NOTIFY_STATUS | Ci.nsIWebProgress.NOTIFY_STATE_ALL Ci.nsIWebProgress.NOTIFY_STATE_ALL
); );
gHeaderCustomize.init(); gHeaderCustomize.init();
@ -523,38 +523,31 @@ var MsgHdrViewObserver = {
* Receives a message's headers as we display the message through our mime converter. * Receives a message's headers as we display the message through our mime converter.
* *
* @see {nsIMailChannel} * @see {nsIMailChannel}
* @implements {nsIMailProgressListener}
* @implements {nsIWebProgressListener} * @implements {nsIWebProgressListener}
* @implements {nsISupportsWeakReference} * @implements {nsISupportsWeakReference}
*/ */
var messageProgressListener = { var messageProgressListener = {
QueryInterface: ChromeUtils.generateQI([ QueryInterface: ChromeUtils.generateQI([
"nsIMailProgressListener",
"nsIWebProgressListener", "nsIWebProgressListener",
"nsISupportsWeakReference", "nsISupportsWeakReference",
]), ]),
/** /**
* @param {nsIWebProgress} webProgress * @param {nsIMailChannel} mailChannel
* @param {nsIRequest} request * @see {nsIMailProgressListener}
* @param {nsresult} status
* @param {wstring} message
* @see {nsIWebProgressListener}
*/ */
onStatusChange(webProgress, request, status, message) { onHeadersComplete(mailChannel) {
// We use status change events as a notification from the parser that the this.processHeaders(mailChannel.headerNames, mailChannel.headerValues);
// headers are ready to use. Though this event could be fired for any },
// of the resources in the message, the first three arguments aren't given
// so there's no way to know. We'll assume that the first time we get here /**
// after STATE_START is the notification from the parser, since at that * @param {nsIMailChannel} mailChannel
// point there should be no other requests in progress. * @see {nsIMailProgressListener}
const docRequest = getMessagePaneBrowser().webProgress?.documentRequest; */
if ( onBodyComplete(mailChannel) {
!this._sawHeaders && autoMarkAsRead();
docRequest &&
docRequest instanceof Ci.nsIMailChannel
) {
this._sawHeaders = true;
this.processHeaders(docRequest.headerNames, docRequest.headerValues);
}
}, },
/** /**
@ -578,17 +571,12 @@ var messageProgressListener = {
ClearAttachmentList(); ClearAttachmentList();
gMessageNotificationBar.clearMsgNotifications(); gMessageNotificationBar.clearMsgNotifications();
this._sawHeaders = false; request.listener = this;
request.smimeHeaderSink = smimeHeaderSink; request.smimeHeaderSink = smimeHeaderSink;
this.onStartHeaders(); this.onStartHeaders();
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
currentCharacterSet = request.mailCharacterSet; currentCharacterSet = request.mailCharacterSet;
request.smimeHeaderSink = null; request.smimeHeaderSink = null;
if (!this._sawHeaders) {
// This shouldn't happen, but it's here as a backup plan.
this._sawHeaders = true;
this.processHeaders(request.headerNames, request.headerValues);
}
if (request.imipItem) { if (request.imipItem) {
calImipBar.showImipBar(request.imipItem, request.imipMethod); calImipBar.showImipBar(request.imipItem, request.imipMethod);
} }
@ -4191,14 +4179,22 @@ function OnMsgLoaded(aUrl) {
return; return;
} }
let win =
location.href == "about:message"
? window.browsingContext.topChromeWindow
: window;
let wintype = win.document.documentElement.getAttribute("windowtype");
gMessageNotificationBar.setJunkMsg(gMessage); gMessageNotificationBar.setJunkMsg(gMessage);
// See if MDN was requested but has not been sent.
HandleMDNResponse(aUrl);
}
/**
* Marks the message as read, optionally after a delay, if the preferences say
* we should do so.
*/
function autoMarkAsRead() {
if (!gMessage?.folder) {
// The message can't be marked read or unread.
return;
}
let markReadAutoMode = Services.prefs.getBoolPref( let markReadAutoMode = Services.prefs.getBoolPref(
"mailnews.mark_message_read.auto" "mailnews.mark_message_read.auto"
); );
@ -4206,14 +4202,15 @@ function OnMsgLoaded(aUrl) {
// We just finished loading a message. If messages are to be marked as read // We just finished loading a message. If messages are to be marked as read
// automatically, set a timer to mark the message is read after n seconds // automatically, set a timer to mark the message is read after n seconds
// where n can be configured by the user. // where n can be configured by the user.
if (gMessage && !gMessage.isRead && markReadAutoMode) { if (!gMessage.isRead && markReadAutoMode) {
let markReadOnADelay = Services.prefs.getBoolPref( let markReadOnADelay = Services.prefs.getBoolPref(
"mailnews.mark_message_read.delay" "mailnews.mark_message_read.delay"
); );
let winType = top.document.documentElement.getAttribute("windowtype");
// Only use the timer if viewing using the 3-pane preview pane and the // Only use the timer if viewing using the 3-pane preview pane and the
// user has set the pref. // user has set the pref.
if (markReadOnADelay && wintype == "mail:3pane") { if (markReadOnADelay && winType == "mail:3pane") {
// 3-pane window // 3-pane window
ClearPendingReadTimer(); ClearPendingReadTimer();
let markReadDelayTime = Services.prefs.getIntPref( let markReadDelayTime = Services.prefs.getIntPref(
@ -4233,9 +4230,6 @@ function OnMsgLoaded(aUrl) {
MarkMessageAsRead(gMessage); MarkMessageAsRead(gMessage);
} }
} }
// See if MDN was requested but has not been sent.
HandleMDNResponse(aUrl);
} }
/** /**

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

@ -7,6 +7,10 @@
#include "nsIPropertyBag2.idl" #include "nsIPropertyBag2.idl"
#include "nsIMsgSMIMEHeaderSink.idl" #include "nsIMsgSMIMEHeaderSink.idl"
interface nsIMailProgressListener;
interface nsIWebProgress;
interface nsIRequest;
/** /**
* An interface that email-streaming channels can use to provide access to * An interface that email-streaming channels can use to provide access to
* parsed message headers, message attachment info, and other metadata. * parsed message headers, message attachment info, and other metadata.
@ -75,4 +79,25 @@ interface nsIMailChannel : nsISupports {
* Set this in onStartRequest to receive security status notifications. * Set this in onStartRequest to receive security status notifications.
*/ */
attribute nsIMsgSMIMEHeaderSink smimeHeaderSink; attribute nsIMsgSMIMEHeaderSink smimeHeaderSink;
/**
* A listener for progress events. This object must also implement
* nsISupportsWeakReference.
*/
attribute nsIMailProgressListener listener;
};
[scriptable, uuid(1286f969-1c20-422e-8247-233fe0d26ba5)]
interface nsIMailProgressListener : nsISupports {
/**
* Receive a notification from the parser that it has finished outputting
* the headers to the channel.
*/
void onHeadersComplete(in nsIMailChannel mailChannel);
/**
* Receive a notification from the parser that it has finished outputting
* the message body to the channel.
*/
void onBodyComplete(in nsIMailChannel mailChannel);
}; };

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

@ -10,6 +10,7 @@ export class MailChannel {
_headerValues = []; _headerValues = [];
_attachments = []; _attachments = [];
_mailCharacterSet = null; _mailCharacterSet = null;
_progressListener = null;
addHeaderFromMIME(name, value) { addHeaderFromMIME(name, value) {
this._headerNames.push(name); this._headerNames.push(name);
@ -59,4 +60,12 @@ export class MailChannel {
imipMethod = null; imipMethod = null;
imipItem = null; imipItem = null;
smimeHeaderSink = null; smimeHeaderSink = null;
get listener() {
return this._progressListener?.get();
}
set listener(listener) {
this._progressListener = Cu.getWeakReference(listener);
}
} }

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

@ -118,3 +118,21 @@ nsMailChannel::SetSmimeHeaderSink(nsIMsgSMIMEHeaderSink* aSmimeHeaderSink) {
mSmimeHeaderSink = aSmimeHeaderSink; mSmimeHeaderSink = aSmimeHeaderSink;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsMailChannel::GetListener(nsIMailProgressListener** aListener) {
nsCOMPtr<nsIMailProgressListener> listener = do_QueryReferent(mListener);
if (listener) {
NS_IF_ADDREF(*aListener = listener);
} else {
*aListener = nullptr;
}
return NS_OK;
}
NS_IMETHODIMP
nsMailChannel::SetListener(nsIMailProgressListener* aListener) {
nsresult rv;
mListener = do_GetWeakReference(aListener, &rv);
return rv;
}

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

@ -10,6 +10,7 @@
#include "nsTArray.h" #include "nsTArray.h"
#include "nsTString.h" #include "nsTString.h"
#include "calIItipItem.h" #include "calIItipItem.h"
#include "nsIWeakReferenceUtils.h"
class nsMailChannel : public nsIMailChannel { class nsMailChannel : public nsIMailChannel {
public: public:
@ -23,6 +24,7 @@ class nsMailChannel : public nsIMailChannel {
nsCString mImipMethod; nsCString mImipMethod;
nsCOMPtr<calIItipItem> mImipItem; nsCOMPtr<calIItipItem> mImipItem;
nsCOMPtr<nsIMsgSMIMEHeaderSink> mSmimeHeaderSink; nsCOMPtr<nsIMsgSMIMEHeaderSink> mSmimeHeaderSink;
nsWeakPtr mListener;
}; };
#endif /* nsMailChannel_h__ */ #endif /* nsMailChannel_h__ */

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

@ -196,23 +196,10 @@ nsresult nsMimeHtmlDisplayEmitter::BroadcastHeaders(int32_t aHeaderMode) {
} }
// Notify the front end that the headers are ready on `mailChannel`. // Notify the front end that the headers are ready on `mailChannel`.
nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; nsCOMPtr<nsIMailProgressListener> listener;
mChannel->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); mailChannel->GetListener(getter_AddRefs(listener));
if (NS_SUCCEEDED(rv) && notificationCallbacks) { if (listener) {
nsCOMPtr<nsIProgressEventSink> sink; listener->OnHeadersComplete(mailChannel);
rv = notificationCallbacks->GetInterface(NS_GET_IID(nsIProgressEventSink),
getter_AddRefs(sink));
if (NS_SUCCEEDED(rv) && sink) {
nsCOMPtr<nsIURI> uri;
mChannel->GetURI(getter_AddRefs(uri));
nsCString host;
uri->GetHost(host);
// The listener gets the localized string "Transferring data from {host}…"
// because we use NS_NET_STATUS_RECEIVING_FROM. The channel and status
// code itself do not get passed to the listener.
sink->OnStatus(mChannel, NS_NET_STATUS_RECEIVING_FROM,
NS_ConvertUTF8toUTF16(host).get());
}
} }
return rv; return rv;
@ -440,24 +427,13 @@ nsresult nsMimeHtmlDisplayEmitter::EndBody() {
} }
// Notify the front end that we've finished reading the body. // Notify the front end that we've finished reading the body.
nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; nsresult rv;
nsresult rv = nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(mChannel, &rv);
mChannel->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); NS_ENSURE_SUCCESS(rv, rv);
if (NS_SUCCEEDED(rv) && notificationCallbacks) { nsCOMPtr<nsIMailProgressListener> listener;
nsCOMPtr<nsIProgressEventSink> sink; mailChannel->GetListener(getter_AddRefs(listener));
rv = notificationCallbacks->GetInterface(NS_GET_IID(nsIProgressEventSink), if (listener) {
getter_AddRefs(sink)); listener->OnBodyComplete(mailChannel);
if (NS_SUCCEEDED(rv) && sink) {
nsCOMPtr<nsIURI> uri;
mChannel->GetURI(getter_AddRefs(uri));
nsCString host;
uri->GetHost(host);
// The listener gets the localized string "Read {host}" because we use
// NS_NET_STATUS_READING. The channel and status code itself do not get
// passed to the listener.
sink->OnStatus(mChannel, NS_NET_STATUS_READING,
NS_ConvertUTF8toUTF16(host).get());
}
} }
return NS_OK; return NS_OK;