Bug 1246517 - Protect better against double removal of "spellcheck-dictionary-remove" observer. r=aceman a=jorgk
authorJorg K
Fri, 12 Feb 2016 12:24:00 +0100
changeset 26767 3b1107f78560cef8b6f09708380778dee1f5cc60
parent 26766 864fb97c88234c0205ca2ad1e5de2472a8540516
child 26768 f44ae06d861353ed780afbcac0434774e74c0ba7
push id1850
push userclokep@gmail.com
push dateWed, 08 Mar 2017 19:29:12 +0000
treeherdercomm-esr52@028df196b2d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaceman, jorgk
bugs1246517
Bug 1246517 - Protect better against double removal of "spellcheck-dictionary-remove" observer. r=aceman a=jorgk
mail/components/compose/content/MsgComposeCommands.js
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -195,17 +195,17 @@ var gComposeRecyclingListener = {
     // Do not listen to changes to spell check dictionary.
     document.removeEventListener("spellcheck-changed", updateDocumentLanguage);
 
     // Disconnect the observer, we'll attach a new one when recycling the
     // window.
     gLanguageObserver.disconnect();
 
     // Stop observing dictionary removals.
-    Services.obs.removeObserver(dictionaryRemovalObserver, "spellcheck-dictionary-remove");
+    dictionaryRemovalObserver.removeObserver();
 
     // Stop gSpellChecker so personal dictionary is saved.
     // We need to do this before disabling the editor.
     enableInlineSpellCheck(false);
     // clear any suggestions in the context menu
     gSpellChecker.clearSuggestionsFromMenu();
     gSpellChecker.clearDictionaryListFromMenu();
 
@@ -2096,16 +2096,34 @@ var dictionaryRemovalObserver =
     if (count == 0 || dictList.includes(prefValue)) {
       language = prefValue;
     } else {
       language = dictList[0];
       // Fix the preference while we're here. We know it's invalid.
       Services.prefs.setCharPref("spellchecker.dictionary", language);
     }
     document.documentElement.setAttribute("lang", language);
+  },
+
+  isAdded: false,
+
+  addObserver: function() {
+    Services.obs.addObserver(this, "spellcheck-dictionary-remove", false);
+    this.isAdded = true;
+  },
+
+  removeObserver: function() {
+    // We need to protect against double removal:
+    // The window can be recycled and later destroyed (at shutdown or when the
+    // composition style changes from HTML to plain text or vice versa) or
+    // only destroyed if it was never recycled before.
+    if (this.isAdded) {
+      Services.obs.removeObserver(this, "spellcheck-dictionary-remove");
+      this.isAdded = false;
+    }
   }
 }
 
 function ComposeStartup(recycled, aParams)
 {
   // Findbar overlay
   if (!document.getElementById("findbar-replaceButton")) {
     let replaceButton = document.createElement("toolbarbutton");
@@ -2171,17 +2189,17 @@ function ComposeStartup(recycled, aParam
       if (mutation.type == "attributes" && mutation.attributeName == "lang") {
         updateLanguageInStatusBar();
       }
     });
   });
   gLanguageObserver.observe(document.documentElement, { attributes: true });
 
   // Observe dictionary removals.
-  Services.obs.addObserver(dictionaryRemovalObserver, "spellcheck-dictionary-remove", false);
+  dictionaryRemovalObserver.addObserver();
 
   // Set document language to the preference as early as possible.
   let languageToSet = getValidSpellcheckerDictionary();
   document.documentElement.setAttribute("lang", languageToSet);
 
   var identityList = document.getElementById("msgIdentity");
 
   document.addEventListener("keypress", awDocumentKeyPress, true);
@@ -2517,23 +2535,17 @@ function ComposeUnload()
   if (gMsgCompose)
     gMsgCompose.removeMsgSendListener(gSendListener);
 
   RemoveMessageComposeOfflineQuitObserver();
   gAttachmentNotifier.shutdown();
   ToolbarIconColor.uninit();
 
   // Stop observing dictionary removals.
-  if (Services.obs.enumerateObservers("spellcheck-dictionary-remove")
-                                     .hasMoreElements()) {
-    // Don't try to remove the observer when at shutdown the recycled window
-    // gets unloaded, its observer was already removed at recycle time.
-    Services.obs.removeObserver(dictionaryRemovalObserver,
-                                "spellcheck-dictionary-remove");
-  }
+  dictionaryRemovalObserver.removeObserver();
 
   if (gMsgCompose)
     gMsgCompose.UnregisterStateListener(stateListener);
   if (gAutoSaveTimeout)
     clearTimeout(gAutoSaveTimeout);
   if (msgWindow)
     msgWindow.closeWindow();