Bug 1416469 - do not call the inline spellchecker if compose window is already closing down. r=jorgk a=jorgk
authoraceman <acelists@atlas.sk>
Tue, 14 Nov 2017 00:08:26 +0100
changeset 29380 be8285843861cdd63f770e7046e540865db8514f
parent 29377 b4b33e1604e113149a9c5401f8248b687a0dd79e
child 29381 b622c91383b8d1420b86786fbd8048461f846979
push id2073
push usermozilla@jorgk.com
push dateTue, 21 Nov 2017 11:24:20 +0000
treeherdercomm-beta@c1f5d46aa961 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorgk, jorgk
bugs1416469
Bug 1416469 - do not call the inline spellchecker if compose window is already closing down. r=jorgk a=jorgk
mail/components/compose/content/MsgComposeCommands.js
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -3071,17 +3071,20 @@ function ComposeUnload()
   // Send notification that the window is going away completely.
   document.getElementById("msgcomposeWindow").dispatchEvent(
     new Event("compose-window-unload", { bubbles: false, cancelable: false }));
 
   GetCurrentCommandManager().removeCommandObserver(gMsgEditorCreationObserver,
                                                    "obs_documentCreated");
   UnloadCommandUpdateHandlers();
 
-  // Stop gSpellChecker so personal dictionary is saved
+  // In some Mozmill tests, the window is closed so quickly that the observer
+  // hasn't fired and removed itself yet, so let's remove it here.
+  spellCheckReadyObserver.removeObserver();
+  // Stop gSpellChecker so personal dictionary is saved.
   enableInlineSpellCheck(false);
 
   EditorCleanup();
 
   if (gMsgCompose)
     gMsgCompose.removeMsgSendListener(gSendListener);
 
   RemoveMessageComposeOfflineQuitObserver();
@@ -3746,51 +3749,98 @@ function addRecipientsToIgnoreList(aAddr
   {
     // break the list of potentially many recipients back into individual names
     var emailAddresses = {};
     var names = {};
     var fullNames = {};
     let numAddresses = MailServices.headerParser.parseHeadersWithArray(aAddressesToAdd, emailAddresses, names, fullNames);
     if (!names)
       return;
-    var tokenizedNames = new Array();
-
-    // each name could consist of multiple word delimited by either commas or spaces. i.e. Green Lantern
+    let tokenizedNames = [];
+
+    // Each name could consist of multiple word delimited by either commas or spaces, i.e. Green Lantern
     // or Lantern,Green. Tokenize on comma first, then tokenize again on spaces.
     for (let name in names.value)
     {
       if (!names.value[name])
         continue;
       let splitNames = names.value[name].split(',');
       for (let i = 0; i < splitNames.length; i++)
       {
         // now tokenize off of white space
         let splitNamesFromWhiteSpaceArray = splitNames[i].split(' ');
         for (let whiteSpaceIndex = 0; whiteSpaceIndex < splitNamesFromWhiteSpaceArray.length; whiteSpaceIndex++)
           if (splitNamesFromWhiteSpaceArray[whiteSpaceIndex])
             tokenizedNames.push(splitNamesFromWhiteSpaceArray[whiteSpaceIndex]);
       }
     }
-
-
-    if (gSpellChecker.mInlineSpellChecker.spellCheckPending)
-    {
+    spellCheckReadyObserver.addWordsToIgnore(tokenizedNames);
+  }
+}
+
+/**
+ * Observer waiting for spell checker to become initialized or done checking.
+ * When it fires, it pushes new words to be ignored to the speller.
+ */
+var spellCheckReadyObserver =
+{
+  _topic: "inlineSpellChecker-spellCheck-ended",
+
+  _ignoreWords: [],
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != this._topic) {
+      return;
+    }
+
+    this.removeObserver();
+    this._addWords();
+  },
+
+  _isAdded: false,
+
+  addObserver: function() {
+    if (this._isAdded)
+      return;
+
+    Services.obs.addObserver(this, this._topic, false);
+    this._isAdded = true;
+  },
+
+  removeObserver: function() {
+    if (!this._isAdded)
+      return;
+
+    Services.obs.removeObserver(this, this._topic);
+    this._clearPendingWords();
+    this._isAdded = false;
+  },
+
+  addWordsToIgnore: function (aIgnoreWords) {
+    this._ignoreWords.push(...aIgnoreWords);
+    if (gSpellChecker.mInlineSpellChecker.spellCheckPending) {
       // spellchecker is enabled, but we must wait for its init to complete
-      Services.obs.addObserver(function observe(subject, topic, data) {
-        if (subject == gMsgCompose.editor)
-        {
-          Services.obs.removeObserver(observe, topic);
-          gSpellChecker.mInlineSpellChecker.ignoreWords(tokenizedNames, tokenizedNames.length);
-        }
-      }, "inlineSpellChecker-spellCheck-ended", false);
+      this.addObserver();
+    } else {
+      this._addWords();
     }
-    else
-    {
-      gSpellChecker.mInlineSpellChecker.ignoreWords(tokenizedNames, tokenizedNames.length);
+  },
+
+  _addWords: function() {
+    // At the time the speller finally got initialized, we may already be closing
+    // the compose together with the speller, so we need to check if they
+    // are still valid.
+    if (gMsgCompose && gSpellChecker.enabled) {
+      gSpellChecker.mInlineSpellChecker.ignoreWords(this._ignoreWords, this._ignoreWords.length);
     }
+    this._clearPendingWords();
+  },
+
+  _clearPendingWords() {
+    this._ignoreWords.length = 0;
   }
 }
 
 function onAddressColCommand(aAddressWidgetId)
 {
   gContentChanged = true;
   awSetAutoComplete(aAddressWidgetId.slice(aAddressWidgetId.lastIndexOf('#') + 1));
   updateSendCommands(true);
@@ -6218,16 +6268,20 @@ function updateDocumentLanguage(e)
 }
 
 // This function modifies gSpellChecker and updates the UI accordingly. It's
 // called either at startup (see InitEditor above), or when the user clicks on
 // one of the two menu items that allow them to toggle the spellcheck feature
 // (either context menu or Options menu).
 function enableInlineSpellCheck(aEnableInlineSpellCheck)
 {
+  if (gSpellChecker.enabled != aEnableInlineSpellCheck) {
+    // If state of spellchecker is about to change, clear any pending observer.
+    spellCheckReadyObserver.removeObserver();
+  }
   gSpellChecker.enabled = aEnableInlineSpellCheck;
   document.getElementById('msgSubject').setAttribute('spellcheck', aEnableInlineSpellCheck);
   document.getElementById("menu_inlineSpellCheck")
           .setAttribute('checked', aEnableInlineSpellCheck);
   document.getElementById("spellCheckEnable")
           .setAttribute('checked', aEnableInlineSpellCheck);
   document.getElementById('spellCheckDictionaries')
           .setAttribute('hidden', !aEnableInlineSpellCheck);