Bug 1732554 - Make GenericSendMessage async. r=mkmelin
authorJohn Bieling <john@thunderbird.net>
Wed, 18 May 2022 13:13:33 +0300
changeset 35764 90328ce5bee2f13de45bdb402054e0288abf7c05
parent 35763 74a4091d1c27a0ba2f3094e95f128bde900ee9a1
child 35765 f4fb3a75992a9417c563953deed3783a0bf4cd1b
push id19933
push usermkmelin@iki.fi
push dateWed, 18 May 2022 10:20:25 +0000
treeherdercomm-central@90328ce5bee2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1732554
Bug 1732554 - Make GenericSendMessage async. r=mkmelin Differential Revision: https://phabricator.services.mozilla.com/D146620
mail/components/compose/content/MsgComposeCommands.js
mail/components/extensions/parent/ext-compose.js
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -5488,17 +5488,17 @@ function SetComposeDetails(newValues) {
   gContentChanged = true;
 }
 
 /**
  * Handles message sending operations.
  *
  * @param {nsIMsgCompDeliverMode} mode - The delivery mode of the operation.
  */
-function GenericSendMessage(msgType) {
+async function GenericSendMessage(msgType) {
   let msgCompFields = GetComposeDetails();
   let subject = msgCompFields.subject;
 
   // Some other msgCompFields have already been updated instantly in their
   // respective toggle functions, e.g. ToggleReturnReceipt(), ToggleDSN(),
   // ToggleAttachVCard(), and toggleAttachmentReminder().
 
   let sending =
@@ -5716,32 +5716,43 @@ function GenericSendMessage(msgType) {
       case Ci.nsIMsgCompSendFormat.Both:
         msgCompFields.forcePlainText = false;
         msgCompFields.useMultipartAlternative = true;
         break;
       default:
         throw new Error(`Invalid send format ${sendFormat}`);
     }
 
-    let beforeSendEvent = new CustomEvent("beforesend", {
-      cancelable: true,
-      detail: msgType,
-    });
-    window.dispatchEvent(beforeSendEvent);
-    if (beforeSendEvent.defaultPrevented) {
+    try {
+      await new Promise((resolve, reject) => {
+        let beforeSendEvent = new CustomEvent("beforesend", {
+          cancelable: true,
+          detail: {
+            resolve,
+            reject,
+          },
+        });
+        window.dispatchEvent(beforeSendEvent);
+        if (!beforeSendEvent.defaultPrevented) {
+          resolve();
+        }
+      });
+    } catch (ex) {
       return;
     }
   }
 
-  CompleteGenericSendMessage(msgType);
+  await CompleteGenericSendMessage(msgType);
 }
 
 /**
  * Finishes message sending. This should ONLY be called directly from
- * GenericSendMessage, or if GenericSendMessage was interrupted by your code.
+ * GenericSendMessage. This is a separate function so that it can be easily mocked
+ * in tests.
+ *
  * @param msgType nsIMsgCompDeliverMode of the operation.
  */
 async function CompleteGenericSendMessage(msgType) {
   // hook for extra compose pre-processing
   Services.obs.notifyObservers(window, "mail:composeOnSend");
 
   if (!gSelectedTechnologyIsPGP) {
     gMsgCompose.compFields.composeSecure.requireEncryptMessage = gSendEncrypted;
@@ -6273,37 +6284,37 @@ async function checkEncryptedBccRecipien
           ignoreButton.callback();
         }
       },
     },
     [ignoreButton]
   );
 }
 
-function SendMessage() {
+async function SendMessage() {
   pillifyRecipients();
   let sendInBackground = Services.prefs.getBoolPref(
     "mailnews.sendInBackground"
   );
   if (sendInBackground && AppConstants.platform != "macosx") {
     let count = [...Services.wm.getEnumerator(null)].length;
     if (count == 1) {
       sendInBackground = false;
     }
   }
 
-  GenericSendMessage(
+  await GenericSendMessage(
     sendInBackground
       ? Ci.nsIMsgCompDeliverMode.Background
       : Ci.nsIMsgCompDeliverMode.Now
   );
   ExitFullscreenMode();
 }
 
-function SendMessageWithCheck() {
+async function SendMessageWithCheck() {
   pillifyRecipients();
   var warn = Services.prefs.getBoolPref("mail.warn_on_send_accel_key");
 
   if (warn) {
     let bundle = getComposeBundle();
     let checkValue = { value: false };
     let buttonPressed = Services.prompt.confirmEx(
       window,
@@ -6332,23 +6343,23 @@ function SendMessageWithCheck() {
   let mode;
   if (Services.io.offline) {
     mode = Ci.nsIMsgCompDeliverMode.Later;
   } else {
     mode = sendInBackground
       ? Ci.nsIMsgCompDeliverMode.Background
       : Ci.nsIMsgCompDeliverMode.Now;
   }
-  GenericSendMessage(mode);
+  await GenericSendMessage(mode);
   ExitFullscreenMode();
 }
 
-function SendMessageLater() {
+async function SendMessageLater() {
   pillifyRecipients();
-  GenericSendMessage(Ci.nsIMsgCompDeliverMode.Later);
+  await GenericSendMessage(Ci.nsIMsgCompDeliverMode.Later);
   ExitFullscreenMode();
 }
 
 function ExitFullscreenMode() {
   // On OS X we need to deliberately exit full screen mode after sending.
   if (AppConstants.platform == "macosx") {
     window.fullScreen = false;
   }
@@ -6376,40 +6387,40 @@ function SaveAsFile(saveAs) {
   if (gMsgCompose.bodyConvertible() == Ci.nsIMsgCompConvertible.Plain) {
     SaveDocument(saveAs, false, "text/plain");
   } else {
     SaveDocument(saveAs, false, "text/html");
   }
   defaultSaveOperation = "file";
 }
 
-function SaveAsDraft() {
+async function SaveAsDraft() {
   gAutoSaveKickedIn = false;
   gEditingDraft = true;
 
-  GenericSendMessage(Ci.nsIMsgCompDeliverMode.SaveAsDraft);
+  await GenericSendMessage(Ci.nsIMsgCompDeliverMode.SaveAsDraft);
   defaultSaveOperation = "draft";
 }
 
-function SaveAsTemplate() {
+async function SaveAsTemplate() {
   gAutoSaveKickedIn = false;
   gEditingDraft = false;
 
   let savedReferences = null;
   if (gMsgCompose && gMsgCompose.compFields) {
     // Clear References header. When we use the template, we don't want that
     // header, yet, "edit as new message" maintains it. So we need to clear
     // it when saving the template.
     // Note: The In-Reply-To header is the last entry in the references header,
     // so it will get cleared as well.
     savedReferences = gMsgCompose.compFields.references;
     gMsgCompose.compFields.references = null;
   }
 
-  GenericSendMessage(Ci.nsIMsgCompDeliverMode.SaveAsTemplate);
+  await GenericSendMessage(Ci.nsIMsgCompDeliverMode.SaveAsTemplate);
   defaultSaveOperation = "template";
 
   if (savedReferences) {
     gMsgCompose.compFields.references = savedReferences;
   }
 }
 
 // Sets the additional FCC, in addition to the default FCC.
@@ -7192,21 +7203,19 @@ function ComposeCanClose() {
         // we close the window ourselves after the save is done.
         gCloseWindowAfterSave = true;
         // We catch the exception because we need to tell toolkit that it
         // shouldn't close the window, because we're going to close it
         // ourselves. If we don't tell toolkit that, and then close the window
         // ourselves, the toolkit code that keeps track of the open windows
         // gets off by one and the app can close unexpectedly on os's that
         // shutdown the app when the last window is closed.
-        try {
-          GenericSendMessage(Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft);
-        } catch (ex) {
-          Cu.reportError(ex);
-        }
+        GenericSendMessage(Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft).catch(
+          Cu.reportError
+        );
         return false;
       case 1: // Cancel
         return false;
       case 2: // Don't Save
         // don't delete the draft if we didn't start off editing a draft
         // and the user hasn't explicitly saved it.
         if (!gEditingDraft && gAutoSaveKickedIn) {
           RemoveDraft();
@@ -10045,24 +10054,24 @@ function loadHTMLMsgPrefs() {
     gDefaultBackgroundColor = bgColor;
     document
       .getElementById("cmd_backgroundColor")
       .setAttribute("state", bgColor);
     onBackgroundColorChange();
   }
 }
 
-function AutoSave() {
+async function AutoSave() {
   if (
     gMsgCompose.editor &&
     (gContentChanged || gMsgCompose.bodyModified) &&
     !gSendOperationInProgress &&
     !gSaveOperationInProgress
   ) {
-    GenericSendMessage(Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft);
+    await GenericSendMessage(Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft);
     gAutoSaveKickedIn = true;
   }
 
   gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
 }
 
 /**
  * Periodically check for keywords in the message.
--- a/mail/components/extensions/parent/ext-compose.js
+++ b/mail/components/extensions/parent/ext-compose.js
@@ -804,45 +804,48 @@ var composeEventTracker = {
     this.listeners.delete(listener);
     if (this.listeners.size == 0) {
       windowTracker.removeListener("beforesend", this);
     }
   },
   async handleEvent(event) {
     event.preventDefault();
 
-    let msgType = event.detail;
     let composeWindow = event.target;
+    composeWindow.ToggleWindowLock(true);
 
-    composeWindow.ToggleWindowLock(true);
+    // Send process waits till sendPromise.resolve() or sendPromise.reject() is
+    // called.
+    let sendPromise = event.detail;
 
     for (let { handler, extension } of this.listeners) {
       let result = await handler(
         composeWindow,
         await getComposeDetails(composeWindow, extension)
       );
       if (!result) {
         continue;
       }
       if (result.cancel) {
         composeWindow.ToggleWindowLock(false);
+        sendPromise.reject();
         return;
       }
       if (result.details) {
         await setComposeDetails(composeWindow, result.details, extension);
       }
     }
 
     // Load the new details into gMsgCompose.compFields for sending.
     composeWindow.GetComposeDetails();
 
     // Calling getComposeDetails collapses mailing lists. Expand them again.
     composeWindow.expandRecipients();
     composeWindow.ToggleWindowLock(false);
-    await composeWindow.CompleteGenericSendMessage(msgType);
+    sendPromise.resolve();
   },
 };
 
 var composeAttachmentTracker = {
   _nextId: 1,
   _attachments: new Map(),
   _attachmentIds: new Map(),