Bug 856270 - Update spell checking to use nsIContentPrefService2 (part 1: core changes). r=ehsan, sr=gavin
authorDrew Willcoxon <adw@mozilla.com>
Wed, 05 Jun 2013 17:07:54 -0700
changeset 134172 8f48c49425ab36aa32130ace7892e4e43db849b5
parent 134171 796a79fc347a878cbb3c32465a45b1a0258c7f05
child 134173 eac7a8c8d5d42d0ca047102422e13a9bb4fec50e
push id24784
push useremorley@mozilla.com
push dateThu, 06 Jun 2013 09:09:24 +0000
treeherdermozilla-central@10a6781c41e4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, gavin
bugs856270
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 856270 - Update spell checking to use nsIContentPrefService2 (part 1: core changes). r=ehsan, sr=gavin
editor/composer/src/nsEditorSpellCheck.cpp
editor/composer/src/nsEditorSpellCheck.h
editor/composer/test/Makefile.in
editor/composer/test/test_async_UpdateCurrentDictionary.html
editor/idl/nsIEditorSpellCheck.idl
editor/txtsvc/public/nsIInlineSpellChecker.idl
extensions/spellcheck/src/mozInlineSpellChecker.cpp
extensions/spellcheck/src/mozInlineSpellChecker.h
layout/build/nsLayoutStatics.cpp
--- a/editor/composer/src/nsEditorSpellCheck.cpp
+++ b/editor/composer/src/nsEditorSpellCheck.cpp
@@ -14,16 +14,17 @@
 #include "nsComponentManagerUtils.h"    // for do_CreateInstance
 #include "nsDebug.h"                    // for NS_ENSURE_TRUE, etc
 #include "nsDependentSubstring.h"       // for Substring
 #include "nsEditorSpellCheck.h"
 #include "nsError.h"                    // for NS_ERROR_NOT_INITIALIZED, etc
 #include "nsIChromeRegistry.h"          // for nsIXULChromeRegistry
 #include "nsIContent.h"                 // for nsIContent
 #include "nsIContentPrefService.h"      // for nsIContentPrefService, etc
+#include "nsIContentPrefService2.h"     // for nsIContentPrefService2, etc
 #include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDOMElement.h"              // for nsIDOMElement
 #include "nsIDOMRange.h"                // for nsIDOMRange
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIEditor.h"                  // for nsIEditor
 #include "nsIHTMLEditor.h"              // for nsIHTMLEditor
 #include "nsILoadContext.h"
 #include "nsISelection.h"               // for nsISelection
@@ -57,44 +58,21 @@ class UpdateDictionnaryHolder {
       if (mSpellCheck) {
         mSpellCheck->EndUpdateDictionary();
       }
     }
 };
 
 #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
 
-class LastDictionary MOZ_FINAL {
-public:
-  /**
-   * Store current dictionary for editor document url. Use content pref
-   * service.
-   */
-  NS_IMETHOD StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary);
-
-  /**
-   * Get last stored current dictionary for editor document url.
-   */
-  NS_IMETHOD FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary);
-
-  /**
-   * Forget last current dictionary stored for editor document url.
-   */
-  NS_IMETHOD ClearCurrentDictionary(nsIEditor* aEditor);
-
-  /**
-   * get uri of editor's document.
-   *
-   */
-  static nsresult GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI);
-};
-
-// static
-nsresult
-LastDictionary::GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
+/**
+ * Gets the URI of aEditor's document.
+ */
+static nsresult
+GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
   NS_ENSURE_ARG_POINTER(aURI);
 
   nsCOMPtr<nsIDOMDocument> domDoc;
   aEditor->GetDocument(getter_AddRefs(domDoc));
   NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
 
@@ -119,100 +97,150 @@ GetLoadContext(nsIEditor* aEditor)
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
   NS_ENSURE_TRUE(doc, nullptr);
 
   nsCOMPtr<nsISupports> container = doc->GetContainer();
   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(container);
   return loadContext.forget();
 }
 
+/**
+ * Fetches the dictionary stored in content prefs and maintains state during the
+ * fetch, which is asynchronous.
+ */
+class DictionaryFetcher MOZ_FINAL : public nsIContentPrefCallback2
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  DictionaryFetcher(nsEditorSpellCheck* aSpellCheck,
+                    nsIEditorSpellCheckCallback* aCallback,
+                    uint32_t aGroup)
+    : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {}
+
+  NS_IMETHOD Fetch(nsIEditor* aEditor);
+
+  NS_IMETHOD HandleResult(nsIContentPref* aPref)
+  {
+    nsCOMPtr<nsIVariant> value;
+    nsresult rv = aPref->GetValue(getter_AddRefs(value));
+    NS_ENSURE_SUCCESS(rv, rv);
+    value->GetAsAString(mDictionary);
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleCompletion(uint16_t reason)
+  {
+    mSpellCheck->DictionaryFetched(this);
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleError(nsresult error)
+  {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
+  uint32_t mGroup;
+  nsString mRootContentLang;
+  nsString mRootDocContentLang;
+  nsString mDictionary;
+
+private:
+  nsCOMPtr<nsEditorSpellCheck> mSpellCheck;
+};
+NS_IMPL_ISUPPORTS1(DictionaryFetcher, nsIContentPrefCallback2)
+
 NS_IMETHODIMP
-LastDictionary::FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary)
+DictionaryFetcher::Fetch(nsIEditor* aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
 
   nsCOMPtr<nsIURI> docUri;
   rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
+  nsAutoCString docUriSpec;
+  rv = docUri->GetSpec(docUriSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
 
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
-  uri->SetAsISupports(docUri);
-
   nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
-  bool hasPref;
-  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, loadContext, &hasPref)) && hasPref) {
-    nsCOMPtr<nsIVariant> pref;
-    contentPrefService->GetPref(uri, CPS_PREF_NAME, loadContext, nullptr, getter_AddRefs(pref));
-    pref->GetAsAString(aDictionary);
-  } else {
-    aDictionary.Truncate();
-  }
+  rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec),
+                                              CPS_PREF_NAME, loadContext,
+                                              this);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-LastDictionary::StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
+/**
+ * Stores the current dictionary for aEditor's document URL.
+ */
+static nsresult
+StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
 
   nsCOMPtr<nsIURI> docUri;
   rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
-  uri->SetAsISupports(docUri);
+  nsAutoCString docUriSpec;
+  rv = docUri->GetSpec(docUriSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
   NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
   prefValue->SetAsAString(aDictionary);
 
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
+  nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
   nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
-  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue, loadContext);
+  return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
+                                 CPS_PREF_NAME, prefValue, loadContext,
+                                 nullptr);
 }
 
-NS_IMETHODIMP
-LastDictionary::ClearCurrentDictionary(nsIEditor* aEditor)
+/**
+ * Forgets the current dictionary stored for aEditor's document URL.
+ */
+static nsresult
+ClearCurrentDictionary(nsIEditor* aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
 
   nsCOMPtr<nsIURI> docUri;
   rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
-  uri->SetAsISupports(docUri);
+  nsAutoCString docUriSpec;
+  rv = docUri->GetSpec(docUriSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
+  nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
   nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
-  return contentPrefService->RemovePref(uri, CPS_PREF_NAME, loadContext);
+  return contentPrefService->RemoveByDomainAndName(
+    NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr);
 }
 
-LastDictionary* nsEditorSpellCheck::gDictionaryStore = nullptr;
-
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
 
 NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
 NS_INTERFACE_MAP_END
@@ -221,16 +249,17 @@ NS_IMPL_CYCLE_COLLECTION_3(nsEditorSpell
                            mEditor,
                            mSpellChecker,
                            mTxtSrvFilter)
 
 nsEditorSpellCheck::nsEditorSpellCheck()
   : mSuggestedWordIndex(0)
   , mDictionaryIndex(0)
   , mEditor(nullptr)
+  , mDictionaryFetcherGroup(0)
   , mUpdateDictionaryRunning(false)
 {
 }
 
 nsEditorSpellCheck::~nsEditorSpellCheck()
 {
   // Make sure we blow the spellchecker away, just in
   // case it hasn't been destroyed already.
@@ -256,29 +285,49 @@ nsEditorSpellCheck::CanSpellCheck(bool* 
   nsTArray<nsString> dictList;
   rv = spellChecker->GetDictionaryList(&dictList);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *_retval = (dictList.Length() > 0);
   return NS_OK;
 }
 
+// Instances of this class can be used as either runnables or RAII helpers.
+class CallbackCaller MOZ_FINAL : public nsRunnable
+{
+public:
+  explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
+    : mCallback(aCallback) {}
+
+  ~CallbackCaller()
+  {
+    Run();
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mCallback) {
+      mCallback->EditorSpellCheckDone();
+      mCallback = nullptr;
+    }
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
+};
+
 NS_IMETHODIMP    
-nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking)
+nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback)
 {
   NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
   mEditor = aEditor;
 
   nsresult rv;
 
-  if (!gDictionaryStore) {
-    gDictionaryStore = new LastDictionary();
-  }
-
-
   // We can spell check with any editor type
   nsCOMPtr<nsITextServicesDocument>tsDoc =
      do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
 
   tsDoc->SetFilter(mTxtSrvFilter);
@@ -340,17 +389,27 @@ nsEditorSpellCheck::InitSpellChecker(nsI
 
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
 
   rv = mSpellChecker->SetDocument(tsDoc, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // do not fail if UpdateCurrentDictionary fails because this method may
   // succeed later.
-  UpdateCurrentDictionary();
+  rv = UpdateCurrentDictionary(aCallback);
+  if (NS_FAILED(rv) && aCallback) {
+    // However, if it does fail, we still need to call the callback since we
+    // discard the failure.  Do it asynchronously so that the caller is always
+    // guaranteed async behavior.
+    nsRefPtr<CallbackCaller> caller = new CallbackCaller(aCallback);
+    NS_ENSURE_STATE(caller);
+    rv = NS_DispatchToMainThread(caller);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::GetNextMisspelledWord(PRUnichar **aNextMisspelledWord)
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
@@ -524,35 +583,41 @@ nsEditorSpellCheck::GetCurrentDictionary
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
   nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
 
+  // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if
+  // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us,
+  // is on the stack.
   if (!mUpdateDictionaryRunning) {
 
+    // Ignore pending dictionary fetchers by increasing this number.
+    mDictionaryFetcherGroup++;
+
     nsDefaultStringComparator comparator;
     nsAutoString langCode;
     int32_t dashIdx = aDictionary.FindChar('-');
     if (dashIdx != -1) {
       langCode.Assign(Substring(aDictionary, 0, dashIdx));
     } else {
       langCode.Assign(aDictionary);
     }
 
     if (mPreferredLang.IsEmpty() || !nsStyleUtil::DashMatchCompare(mPreferredLang, langCode, comparator)) {
       // When user sets dictionary manually, we store this value associated
       // with editor url.
-      gDictionaryStore->StoreCurrentDictionary(mEditor, aDictionary);
+      StoreCurrentDictionary(mEditor, aDictionary);
     } else {
       // If user sets a dictionary matching (even partially), lang defined by
       // document, we consider content pref has been canceled, and we clear it.
-      gDictionaryStore->ClearCurrentDictionary(mEditor);
+      ClearCurrentDictionary(mEditor);
     }
 
     // Also store it in as a preference. It will be used as a default value
     // when everything else fails.
     Preferences::SetString("spellchecker.dictionary", aDictionary);
   }
   return mSpellChecker->SetCurrentDictionary(aDictionary);
 }
@@ -608,58 +673,82 @@ nsresult
 nsEditorSpellCheck::DeleteSuggestedWordList()
 {
   mSuggestedWordList.Clear();
   mSuggestedWordIndex = 0;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsEditorSpellCheck::UpdateCurrentDictionary()
+nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback)
 {
   nsresult rv;
 
   nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
 
-  UpdateDictionnaryHolder holder(this);
-
   // Get language with html5 algorithm
   nsCOMPtr<nsIContent> rootContent;
   nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
   if (htmlEditor) {
     rootContent = htmlEditor->GetActiveEditingHost();
   } else {
     nsCOMPtr<nsIDOMElement> rootElement;
     rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
     NS_ENSURE_SUCCESS(rv, rv);
     rootContent = do_QueryInterface(rootElement);
   }
   NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
 
-  mPreferredLang.Truncate();
-  rootContent->GetLang(mPreferredLang);
+  DictionaryFetcher* fetcher = new DictionaryFetcher(this, aCallback,
+                                                     mDictionaryFetcherGroup);
+  rootContent->GetLang(fetcher->mRootContentLang);
+  nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
+  NS_ENSURE_STATE(doc);
+  doc->GetContentLanguage(fetcher->mRootDocContentLang);
+
+  rv = fetcher->Fetch(mEditor);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
 
-  // Tell the spellchecker what dictionary to use:
+nsresult
+nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
+{
+  nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
+
+  nsresult rv = NS_OK;
 
-  // First try to get dictionary from content prefs. If we have one, do not got
+  // Important: declare the holder after the callback caller so that the former
+  // is destructed first so that it's not active when the callback is called.
+  CallbackCaller callbackCaller(aFetcher->mCallback);
+  UpdateDictionnaryHolder holder(this);
+
+  if (aFetcher->mGroup < mDictionaryFetcherGroup) {
+    // SetCurrentDictionary was called after the fetch started.  Don't overwrite
+    // that dictionary with the fetched one.
+    return NS_OK;
+  }
+
+  mPreferredLang.Assign(aFetcher->mRootContentLang);
+
+  // If we successfully fetched a dictionary from content prefs, do not go
   // further. Use this exact dictionary.
   nsAutoString dictName;
-  rv = gDictionaryStore->FetchLastDictionary(mEditor, dictName);
-  if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
+  dictName.Assign(aFetcher->mDictionary);
+  if (!dictName.IsEmpty()) {
     if (NS_FAILED(SetCurrentDictionary(dictName))) { 
       // may be dictionary was uninstalled ?
-      gDictionaryStore->ClearCurrentDictionary(mEditor);
+      ClearCurrentDictionary(mEditor);
     }
     return NS_OK;
   }
 
   if (mPreferredLang.IsEmpty()) {
-    nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
-    NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
-    doc->GetContentLanguage(mPreferredLang);
+    mPreferredLang.Assign(aFetcher->mRootDocContentLang);
   }
 
   // Then, try to use language computed from element
   if (!mPreferredLang.IsEmpty()) {
     dictName.Assign(mPreferredLang);
   }
 
   // otherwise, get language from preferences
@@ -776,13 +865,8 @@ nsEditorSpellCheck::UpdateCurrentDiction
   // fail silently so that the spellchecker dialog is allowed to come
   // up. The user can manually reset the language to their choice on
   // the dialog if it is wrong.
 
   DeleteSuggestedWordList();
 
   return NS_OK;
 }
-
-void 
-nsEditorSpellCheck::ShutDown() {
-  delete gDictionaryStore;
-}
--- a/editor/composer/src/nsEditorSpellCheck.h
+++ b/editor/composer/src/nsEditorSpellCheck.h
@@ -20,34 +20,32 @@ class nsISpellChecker;
 class nsITextServicesFilter;
 
 #define NS_EDITORSPELLCHECK_CID                     \
 { /* {75656ad9-bd13-4c5d-939a-ec6351eea0cc} */        \
     0x75656ad9, 0xbd13, 0x4c5d,                       \
     { 0x93, 0x9a, 0xec, 0x63, 0x51, 0xee, 0xa0, 0xcc }\
 }
 
-class LastDictionary;
+class DictionaryFetcher;
 
 class nsEditorSpellCheck : public nsIEditorSpellCheck
 {
+  friend class DictionaryFetcher;
+
 public:
   nsEditorSpellCheck();
   virtual ~nsEditorSpellCheck();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsEditorSpellCheck)
 
   /* Declare all methods in the nsIEditorSpellCheck interface */
   NS_DECL_NSIEDITORSPELLCHECK
 
-  static LastDictionary* gDictionaryStore;
-
-  static void ShutDown();
-
 protected:
   nsCOMPtr<nsISpellChecker> mSpellChecker;
 
   nsTArray<nsString>  mSuggestedWordList;
   int32_t        mSuggestedWordIndex;
 
   // these are the words in the current personal dictionary,
   // GetPersonalDictionary must be called to load them.
@@ -56,18 +54,22 @@ protected:
 
   nsresult       DeleteSuggestedWordList();
 
   nsCOMPtr<nsITextServicesFilter> mTxtSrvFilter;
   nsCOMPtr<nsIEditor> mEditor;
 
   nsString mPreferredLang;
 
+  uint32_t mDictionaryFetcherGroup;
+
   bool mUpdateDictionaryRunning;
 
+  nsresult DictionaryFetched(DictionaryFetcher* aFetchState);
+
 public:
   void BeginUpdateDictionary() { mUpdateDictionaryRunning = true ;}
   void EndUpdateDictionary() { mUpdateDictionaryRunning = false ;}
 };
 
 #endif // nsEditorSpellCheck_h___
 
 
--- a/editor/composer/test/Makefile.in
+++ b/editor/composer/test/Makefile.in
@@ -19,11 +19,12 @@ MOCHITEST_FILES = \
 		bug678842_subframe.html \
 		test_bug738440.html \
 		$(NULL)
 
 MOCHITEST_CHROME_FILES = \
 		test_bug434998.xul \
 		test_bug338427.html \
 		test_bug678842.html \
+		test_async_UpdateCurrentDictionary.html \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/editor/composer/test/test_async_UpdateCurrentDictionary.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=856270
+-->
+<head>
+  <title>Test for Bug 856270 - Async UpdateCurrentDictionary</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=856270">Mozilla Bug 856270</a>
+<p id="display"></p>
+<div id="content">
+<textarea id="editor" spellcheck="true"></textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(start);
+
+function start() {
+  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
+  var textarea = document.getElementById("editor");
+  textarea.focus();
+
+  onSpellCheck(textarea, function () {
+    var isc = textarea.editor.getInlineSpellChecker(false);
+    ok(isc, "Inline spell checker should exist after focus and spell check");
+    var sc = isc.spellChecker;
+    isnot(sc.GetCurrentDictionary(), lang,
+          "Current dictionary should not be set yet.");
+
+    // First, set the lang attribute on the textarea, call Update, and make
+    // sure the spell checker's language was updated appropriately.
+    var lang = "en-US";
+    textarea.setAttribute("lang", lang);
+    sc.UpdateCurrentDictionary(function () {
+      is(sc.GetCurrentDictionary(), lang,
+         "UpdateCurrentDictionary should set the current dictionary.");
+
+      // Second, make some Update calls, but then do a Set.  The Set should
+      // effectively cancel the Updates, but the Updates' callbacks should be
+      // called nonetheless.
+      var numCalls = 3;
+      for (var i = 0; i < numCalls; i++) {
+        sc.UpdateCurrentDictionary(function () {
+          is(sc.GetCurrentDictionary(), "",
+             "No dictionary should be active after Update.");
+          if (--numCalls == 0)
+            SimpleTest.finish();
+        });
+      }
+      try {
+        sc.SetCurrentDictionary("testing-XX");
+      }
+      catch (err) {
+        // Set throws NS_ERROR_NOT_AVAILABLE because "testing-XX" isn't really
+        // an available dictionary.
+      }
+      is(sc.GetCurrentDictionary(), "",
+         "No dictionary should be active after Set.");
+    });
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/editor/idl/nsIEditorSpellCheck.idl
+++ b/editor/idl/nsIEditorSpellCheck.idl
@@ -2,18 +2,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  
 #include "nsISupports.idl"
 
 interface nsIEditor;
 interface nsITextServicesFilter;
+interface nsIEditorSpellCheckCallback;
 
-[scriptable, uuid(334946c3-0e93-4aac-b662-e1b56f95d68b)]
+[scriptable, uuid(dd32ef3b-a7d8-43d1-9617-5f2dddbe29eb)]
 interface nsIEditorSpellCheck : nsISupports
 {
 
   /**
    * Call this on any change in installed dictionaries to ensure that the spell
    * checker is not using a current dictionary which is no longer available.
    * If the current dictionary is no longer available, then pick another one.
    */
@@ -25,19 +26,21 @@ interface nsIEditorSpellCheck : nsISuppo
    */
   boolean       canSpellCheck();
 
   /**
    * Turns on the spell checker for the given editor. enableSelectionChecking
    * set means that we only want to check the current selection in the editor,
    * (this controls the behavior of GetNextMisspelledWord). For spellchecking
    * clients with no modal UI (such as inline spellcheckers), this flag doesn't
-   * matter
+   * matter.  Initialization is asynchronous and is not complete until the given
+   * callback is called.
    */
-  void          InitSpellChecker(in nsIEditor editor, in boolean enableSelectionChecking);
+  void          InitSpellChecker(in nsIEditor editor, in boolean enableSelectionChecking,
+                                 [optional] in nsIEditorSpellCheckCallback callback);
 
   /**
    * When interactively spell checking the document, this will return the
    * value of the next word that is misspelled. This also computes the
    * suggestions which you can get by calling GetSuggestedWord.
    *
    * @see nsISpellChecker::GetNextMisspelledWord
    */
@@ -151,13 +154,20 @@ interface nsIEditorSpellCheck : nsISuppo
    * Watch out: this does not clear any suggestions left over from previous
    * calls to CheckCurrentWord, so there may be suggestions, but they will be
    * invalid.
    */
   boolean       CheckCurrentWordNoSuggest(in wstring suggestedWord);
 
   /**
    * Update the dictionary in use to be sure it corresponds to what the editor
-   * needs.
+   * needs.  The update is asynchronous and is not complete until the given
+   * callback is called.
    */
-  void          UpdateCurrentDictionary();
+  void          UpdateCurrentDictionary([optional] in nsIEditorSpellCheckCallback callback);
 
 };
+
+[scriptable, function, uuid(5f0a4bab-8538-4074-89d3-2f0e866a1c0b)]
+interface nsIEditorSpellCheckCallback : nsISupports
+{
+  void editorSpellCheckDone();
+};
--- a/editor/txtsvc/public/nsIInlineSpellChecker.idl
+++ b/editor/txtsvc/public/nsIInlineSpellChecker.idl
@@ -5,18 +5,17 @@
 
 #include "nsISupports.idl"
 #include "domstubs.idl"
 
 interface nsISelection;
 interface nsIEditor;
 interface nsIEditorSpellCheck;
 
-[scriptable, uuid(df635540-d073-47b8-8678-18776130691d)]
-
+[scriptable, uuid(b7b7a77c-40c4-4196-b0b7-b0338243b3fe)]
 interface nsIInlineSpellChecker : nsISupports
 {
   readonly attribute nsIEditorSpellCheck spellChecker;
 
   void init(in nsIEditor aEditor);
   void cleanup(in boolean aDestroyingFrames);
 
   attribute boolean enableRealTimeSpell;
@@ -35,15 +34,17 @@ interface nsIInlineSpellChecker : nsISup
   nsIDOMRange getMisspelledWord(in nsIDOMNode aNode, in long aOffset);
   void replaceWord(in nsIDOMNode aNode, in long aOffset, in AString aNewword);
   void addWordToDictionary(in AString aWord);
   void removeWordFromDictionary(in AString aWord);
   
   void ignoreWord(in AString aWord);
   void ignoreWords([array, size_is(aCount)] in wstring aWordsToIgnore, in unsigned long aCount);
   void updateCurrentDictionary();
+
+  readonly attribute boolean spellCheckPending;
 };
 
 %{C++
 
 #define MOZ_INLINESPELLCHECKER_CONTRACTID "@mozilla.org/spellchecker-inline;1"
 
 %}
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -57,16 +57,18 @@
 #include "nsThreadUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsIContent.h"
 #include "nsEventListenerManager.h"
 #include "nsGUIEvent.h"
 #include "nsRange.h"
 #include "nsContentUtils.h"
 #include "nsEditor.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
 
 using namespace mozilla::dom;
 
 // Set to spew messages to the console about what is happening.
 //#define DEBUG_INLINESPELL
 
 // the number of milliseconds that we will take at once to do spellchecking
 #define INLINESPELL_CHECK_TIMEOUT 50
@@ -79,16 +81,20 @@ using namespace mozilla::dom;
 
 // This number is the number of checked words a misspelled word counts for
 // when we're checking the time to see if the alloted time is up for
 // spellchecking. Misspelled words take longer to process since we have to
 // create a range, so they count more. The exact number isn't very important
 // since this just controls how often we check the current time.
 #define MISSPELLED_WORD_COUNT_PENALTY 4
 
+// These notifications are broadcast when spell check starts and ends.  STARTED
+// must always be followed by ENDED.
+#define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started"
+#define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended"
 
 static bool ContentIsDescendantOf(nsINode* aPossibleDescendant,
                                     nsINode* aPossibleAncestor);
 
 static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings";
 
 mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
     : mSpellChecker(aSpellChecker), mWordCount(0)
@@ -450,28 +456,38 @@ mozInlineSpellStatus::PositionToCollapse
   return NS_OK;
 }
 
 // mozInlineSpellResume
 
 class mozInlineSpellResume : public nsRunnable
 {
 public:
-  mozInlineSpellResume(const mozInlineSpellStatus& aStatus) : mStatus(aStatus) {}
-  mozInlineSpellStatus mStatus;
+  mozInlineSpellResume(const mozInlineSpellStatus& aStatus,
+                       uint32_t aDisabledAsyncToken)
+    : mDisabledAsyncToken(aDisabledAsyncToken), mStatus(aStatus) {}
+
   nsresult Post()
   {
     return NS_DispatchToMainThread(this);
   }
 
   NS_IMETHOD Run()
   {
-    mStatus.mSpellChecker->ResumeCheck(&mStatus);
+    // Discard the resumption if the spell checker was disabled after the
+    // resumption was scheduled.
+    if (mDisabledAsyncToken == mStatus.mSpellChecker->mDisabledAsyncToken) {
+      mStatus.mSpellChecker->ResumeCheck(&mStatus);
+    }
     return NS_OK;
   }
+
+private:
+  uint32_t mDisabledAsyncToken;
+  mozInlineSpellStatus mStatus;
 };
 
 
 NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker)
   NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker)
   NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
@@ -489,16 +505,19 @@ NS_IMPL_CYCLE_COLLECTION_3(mozInlineSpel
 
 mozInlineSpellChecker::SpellCheckingState
   mozInlineSpellChecker::gCanEnableSpellChecking =
   mozInlineSpellChecker::SpellCheck_Uninitialized;
 
 mozInlineSpellChecker::mozInlineSpellChecker() :
     mNumWordsInSpellSelection(0),
     mMaxNumWordsInSpellSelection(250),
+    mNumPendingSpellChecks(0),
+    mNumPendingUpdateCurrentDictionary(0),
+    mDisabledAsyncToken(0),
     mNeedsCheckAfterNavigation(false),
     mFullSpellCheckScheduled(false)
 {
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs)
     prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection); 
   mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4;
 }
@@ -645,55 +664,192 @@ mozInlineSpellChecker::UnregisterEventLi
 }
 
 // mozInlineSpellChecker::GetEnableRealTimeSpell
 
 NS_IMETHODIMP
 mozInlineSpellChecker::GetEnableRealTimeSpell(bool* aEnabled)
 {
   NS_ENSURE_ARG_POINTER(aEnabled);
-  *aEnabled = mSpellCheck != nullptr;
+  *aEnabled = mSpellCheck != nullptr || mPendingSpellCheck != nullptr;
   return NS_OK;
 }
 
+// Used as the nsIEditorSpellCheck::InitSpellChecker callback.
+class InitEditorSpellCheckCallback MOZ_FINAL : public nsIEditorSpellCheckCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit InitEditorSpellCheckCallback(mozInlineSpellChecker* aSpellChecker)
+    : mSpellChecker(aSpellChecker) {}
+
+  NS_IMETHOD EditorSpellCheckDone()
+  {
+    return mSpellChecker ? mSpellChecker->EditorSpellCheckInited() : NS_OK;
+  }
+
+  void Cancel()
+  {
+    mSpellChecker = nullptr;
+  }
+
+private:
+  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
+};
+NS_IMPL_ISUPPORTS1(InitEditorSpellCheckCallback, nsIEditorSpellCheckCallback)
+
 // mozInlineSpellChecker::SetEnableRealTimeSpell
 
 NS_IMETHODIMP
 mozInlineSpellChecker::SetEnableRealTimeSpell(bool aEnabled)
 {
   if (!aEnabled) {
     mSpellCheck = nullptr;
-    return Cleanup(false);
+
+    // Hold on to mEditor since Cleanup nulls it out.  See below.
+    nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
+
+    nsresult rv = Cleanup(false);
+
+    // Notify ENDED observers now.  If we wait to notify as we normally do when
+    // these async operations finish, then in the meantime the editor may create
+    // another inline spell checker and cause more STARTED and ENDED
+    // notifications to be broadcast.  Interleaved notifications for the same
+    // editor but different inline spell checkers could easily confuse
+    // observers.  They may receive two consecutive STARTED notifications for
+    // example, which we guarantee will not happen.  Plus, mEditor must always
+    // be passed to observers.  If we wait to notify, we'd have to hold on to
+    // mEditor because Cleanup nulls it out.
+
+    if (mPendingSpellCheck) {
+      // Cancel the pending editor spell checker initialization.
+      mPendingSpellCheck = nullptr;
+      mPendingInitEditorSpellCheckCallback->Cancel();
+      mPendingInitEditorSpellCheckCallback = nullptr;
+      ChangeNumPendingSpellChecks(-1, editor);
+    }
+
+    // Increment this token so that pending UpdateCurrentDictionary calls and
+    // scheduled spell checks are discarded when they finish.
+    mDisabledAsyncToken++;
+
+    if (mNumPendingUpdateCurrentDictionary > 0) {
+      // Account for pending UpdateCurrentDictionary calls.
+      ChangeNumPendingSpellChecks(-mNumPendingUpdateCurrentDictionary, editor);
+      mNumPendingUpdateCurrentDictionary = 0;
+    }
+    if (mNumPendingSpellChecks > 0) {
+      // If mNumPendingSpellChecks is still > 0 at this point, the remainder is
+      // pending scheduled spell checks.
+      ChangeNumPendingSpellChecks(-mNumPendingSpellChecks, editor);
+    }
+
+    return rv;
+  }
+
+  if (mSpellCheck) {
+    // spellcheck the current contents. SpellCheckRange doesn't supply a created
+    // range to DoSpellCheck, which in our case is the entire range. But this
+    // optimization doesn't matter because there is nothing in the spellcheck
+    // selection when starting, which triggers a better optimization.
+    return SpellCheckRange(nullptr);
   }
 
-  if (!mSpellCheck) {
-    nsresult res = NS_OK;
-    nsCOMPtr<nsIEditorSpellCheck> spellchecker = do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &res);
-    if (NS_SUCCEEDED(res) && spellchecker)
-    {
-      nsCOMPtr<nsITextServicesFilter> filter = do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1", &res);
-      spellchecker->SetFilter(filter);
-      nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
-      res = spellchecker->InitSpellChecker(editor, false);
-      NS_ENSURE_SUCCESS(res, res);
+  if (mPendingSpellCheck) {
+    // The editor spell checker is already being initialized.
+    return NS_OK;
+  }
+
+  mPendingSpellCheck =
+    do_CreateInstance("@mozilla.org/editor/editorspellchecker;1");
+  NS_ENSURE_STATE(mPendingSpellCheck);
+
+  nsCOMPtr<nsITextServicesFilter> filter =
+    do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1");
+  if (!filter) {
+    mPendingSpellCheck = nullptr;
+    NS_ENSURE_STATE(filter);
+  }
+  mPendingSpellCheck->SetFilter(filter);
+
+  mPendingInitEditorSpellCheckCallback = new InitEditorSpellCheckCallback(this);
+  if (!mPendingInitEditorSpellCheckCallback) {
+    mPendingSpellCheck = nullptr;
+    NS_ENSURE_STATE(mPendingInitEditorSpellCheckCallback);
+  }
 
-      mSpellCheck = spellchecker;
+  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
+  nsresult rv = mPendingSpellCheck->InitSpellChecker(
+                  editor, false, mPendingInitEditorSpellCheckCallback);
+  if (NS_FAILED(rv)) {
+    mPendingSpellCheck = nullptr;
+    mPendingInitEditorSpellCheckCallback = nullptr;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  ChangeNumPendingSpellChecks(1);
+
+  return NS_OK;
+}
 
-      // spell checking is enabled, register our event listeners to track navigation
-      RegisterEventListeners();
-    }
-  }
+// Called when nsIEditorSpellCheck::InitSpellChecker completes.
+nsresult
+mozInlineSpellChecker::EditorSpellCheckInited()
+{
+  NS_ASSERTION(mPendingSpellCheck, "Spell check should be pending!");
+
+  // spell checking is enabled, register our event listeners to track navigation
+  RegisterEventListeners();
+
+  mSpellCheck = mPendingSpellCheck;
+  mPendingSpellCheck = nullptr;
+  mPendingInitEditorSpellCheckCallback = nullptr;
+  ChangeNumPendingSpellChecks(-1);
 
   // spellcheck the current contents. SpellCheckRange doesn't supply a created
   // range to DoSpellCheck, which in our case is the entire range. But this
   // optimization doesn't matter because there is nothing in the spellcheck
   // selection when starting, which triggers a better optimization.
   return SpellCheckRange(nullptr);
 }
 
+// Changes the number of pending spell checks by the given delta.  If the number
+// becomes zero or nonzero, observers are notified.  See NotifyObservers for
+// info on the aEditor parameter.
+void
+mozInlineSpellChecker::ChangeNumPendingSpellChecks(int32_t aDelta,
+                                                   nsIEditor* aEditor)
+{
+  int8_t oldNumPending = mNumPendingSpellChecks;
+  mNumPendingSpellChecks += aDelta;
+  NS_ASSERTION(mNumPendingSpellChecks >= 0,
+               "Unbalanced ChangeNumPendingSpellChecks calls!");
+  if (oldNumPending == 0 && mNumPendingSpellChecks > 0) {
+    NotifyObservers(INLINESPELL_STARTED_TOPIC, aEditor);
+  } else if (oldNumPending > 0 && mNumPendingSpellChecks == 0) {
+    NotifyObservers(INLINESPELL_ENDED_TOPIC, aEditor);
+  }
+}
+
+// Broadcasts the given topic to observers.  aEditor is passed to observers if
+// nonnull; otherwise mEditor is passed.
+void
+mozInlineSpellChecker::NotifyObservers(const char* aTopic, nsIEditor* aEditor)
+{
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (!os)
+    return;
+  nsCOMPtr<nsIEditor> editor = aEditor;
+  if (!editor) {
+    editor = do_QueryReferent(mEditor);
+  }
+  os->NotifyObservers(editor, aTopic, nullptr);
+}
+
 // mozInlineSpellChecker::SpellCheckAfterEditorChange
 //
 //    Called by the editor when nearly anything happens to change the content.
 //
 //    The start and end positions specify a range for the thing that happened,
 //    but these are usually nullptr, even when you'd think they would be useful
 //    because you want the range (for example, pasting). We ignore them in
 //    this case.
@@ -1122,26 +1278,30 @@ mozInlineSpellChecker::SkipSpellCheckFor
 nsresult
 mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus& aStatus)
 {
   if (mFullSpellCheckScheduled) {
     // Just ignore this; we're going to spell-check everything anyway
     return NS_OK;
   }
 
-  mozInlineSpellResume* resume = new mozInlineSpellResume(aStatus);
+  mozInlineSpellResume* resume =
+    new mozInlineSpellResume(aStatus, mDisabledAsyncToken);
   NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = resume->Post();
   if (NS_FAILED(rv)) {
     delete resume;
-  } else if (aStatus.IsFullSpellCheck()) {
-    // We're going to check everything.  Suppress further spell-check attempts
-    // until that happens.
-    mFullSpellCheckScheduled = true;
+  } else {
+    if (aStatus.IsFullSpellCheck()) {
+      // We're going to check everything.  Suppress further spell-check attempts
+      // until that happens.
+      mFullSpellCheckScheduled = true;
+    }
+    ChangeNumPendingSpellChecks(1);
   }
   return rv;
 }
 
 // mozInlineSpellChecker::DoSpellCheckSelection
 //
 //    Called to re-check all misspelled words. We iterate over all ranges in
 //    the selection and call DoSpellCheck on them. This is used when a word
@@ -1413,24 +1573,48 @@ nsresult mozInlineSpellChecker::DoSpellC
         return NS_OK;
       }
     }
   }
 
   return NS_OK;
 }
 
+// An RAII helper that calls ChangeNumPendingSpellChecks on destruction.
+class AutoChangeNumPendingSpellChecks
+{
+public:
+  AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker,
+                                  int32_t aDelta)
+    : mSpellChecker(aSpellChecker), mDelta(aDelta) {}
+
+  ~AutoChangeNumPendingSpellChecks()
+  {
+    mSpellChecker->ChangeNumPendingSpellChecks(mDelta);
+  }
+
+private:
+  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
+  int32_t mDelta;
+};
+
 // mozInlineSpellChecker::ResumeCheck
 //
 //    Called by the resume event when it fires. We will try to pick up where
 //    the last resume left off.
 
 nsresult
 mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus)
 {
+  // Observers should be notified that spell check has ended only after spell
+  // check is done below, but since there are many early returns in this method
+  // and the number of pending spell checks must be decremented regardless of
+  // whether the spell check actually happens, use this RAII object.
+  AutoChangeNumPendingSpellChecks autoChangeNumPending(this, -1);
+
   if (aStatus->IsFullSpellCheck()) {
     // Allow posting new spellcheck resume events from inside
     // ResumeCheck, now that we're actually firing.
     NS_ASSERTION(mFullSpellCheckScheduled,
                  "How could this be false?  The full spell check is "
                  "calling us!!");
     mFullSpellCheckScheduled = false;
   }
@@ -1756,34 +1940,86 @@ nsresult mozInlineSpellChecker::KeyPress
     case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
       HandleNavigationEvent(true /* force a spelling correction */);
       break;
   }
 
   return NS_OK;
 }
 
+// Used as the nsIEditorSpellCheck::UpdateCurrentDictionary callback.
+class UpdateCurrentDictionaryCallback MOZ_FINAL : public nsIEditorSpellCheckCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit UpdateCurrentDictionaryCallback(mozInlineSpellChecker* aSpellChecker,
+                                           uint32_t aDisabledAsyncToken)
+    : mSpellChecker(aSpellChecker), mDisabledAsyncToken(aDisabledAsyncToken) {}
+
+  NS_IMETHOD EditorSpellCheckDone()
+  {
+    // Ignore this callback if SetEnableRealTimeSpell(false) was called after
+    // the UpdateCurrentDictionary call that triggered it.
+    return mSpellChecker->mDisabledAsyncToken > mDisabledAsyncToken ?
+           NS_OK :
+           mSpellChecker->CurrentDictionaryUpdated();
+  }
+
+private:
+  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
+  uint32_t mDisabledAsyncToken;
+};
+NS_IMPL_ISUPPORTS1(UpdateCurrentDictionaryCallback, nsIEditorSpellCheckCallback)
+
 NS_IMETHODIMP mozInlineSpellChecker::UpdateCurrentDictionary()
 {
   if (!mSpellCheck) {
     return NS_OK;
   }
 
-  nsAutoString previousDictionary;
-  if (NS_FAILED(mSpellCheck->GetCurrentDictionary(previousDictionary))) {
-    previousDictionary.Truncate();
+  if (NS_FAILED(mSpellCheck->GetCurrentDictionary(mPreviousDictionary))) {
+    mPreviousDictionary.Truncate();
   }
 
-  // This might set mSpellCheck to null (bug 793866)
-  nsresult rv = mSpellCheck->UpdateCurrentDictionary();
+  nsRefPtr<UpdateCurrentDictionaryCallback> cb =
+    new UpdateCurrentDictionaryCallback(this, mDisabledAsyncToken);
+  NS_ENSURE_STATE(cb);
+  nsresult rv = mSpellCheck->UpdateCurrentDictionary(cb);
+  if (NS_FAILED(rv)) {
+    cb = nullptr;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  mNumPendingUpdateCurrentDictionary++;
+  ChangeNumPendingSpellChecks(1);
+
+  return NS_OK;
+}
+
+// Called when nsIEditorSpellCheck::UpdateCurrentDictionary completes.
+nsresult mozInlineSpellChecker::CurrentDictionaryUpdated()
+{
+  mNumPendingUpdateCurrentDictionary--;
+  NS_ASSERTION(mNumPendingUpdateCurrentDictionary >= 0,
+               "CurrentDictionaryUpdated called without corresponding "
+               "UpdateCurrentDictionary call!");
+  ChangeNumPendingSpellChecks(-1);
 
   nsAutoString currentDictionary;
   if (!mSpellCheck ||
       NS_FAILED(mSpellCheck->GetCurrentDictionary(currentDictionary))) {
     currentDictionary.Truncate();
   }
 
-  if (!previousDictionary.Equals(currentDictionary)) {
-      rv = SpellCheckRange(nullptr);
+  if (!mPreviousDictionary.Equals(currentDictionary)) {
+    nsresult rv = SpellCheckRange(nullptr);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  return rv;
+  return NS_OK;
 }
+
+NS_IMETHODIMP
+mozInlineSpellChecker::GetSpellCheckPending(bool* aPending)
+{
+  *aPending = mNumPendingSpellChecks > 0;
+  return NS_OK;
+}
--- a/extensions/spellcheck/src/mozInlineSpellChecker.h
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.h
@@ -23,16 +23,19 @@
 #ifdef KeyPress
 #undef KeyPress
 #endif
 
 class nsIDOMMouseEventListener;
 class mozInlineSpellWordUtil;
 class mozInlineSpellChecker;
 class mozInlineSpellResume;
+class InitEditorSpellCheckCallback;
+class UpdateCurrentDictionaryCallback;
+class mozInlineSpellResume;
 
 class mozInlineSpellStatus
 {
 public:
   mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker);
 
   nsresult InitForEditorChange(EditAction aAction,
                                nsIDOMNode* aAnchorNode, int32_t aAnchorOffset,
@@ -115,25 +118,30 @@ protected:
 
 class mozInlineSpellChecker : public nsIInlineSpellChecker,
                               public nsIEditActionListener,
                               public nsIDOMEventListener,
                               public nsSupportsWeakReference
 {
 private:
   friend class mozInlineSpellStatus;
+  friend class InitEditorSpellCheckCallback;
+  friend class UpdateCurrentDictionaryCallback;
+  friend class AutoChangeNumPendingSpellChecks;
+  friend class mozInlineSpellResume;
 
   // Access with CanEnableInlineSpellChecking
   enum SpellCheckingState { SpellCheck_Uninitialized = -1,
                             SpellCheck_NotAvailable = 0,
                             SpellCheck_Available = 1};
   static SpellCheckingState gCanEnableSpellChecking;
 
   nsWeakPtr mEditor; 
   nsCOMPtr<nsIEditorSpellCheck> mSpellCheck;
+  nsCOMPtr<nsIEditorSpellCheck> mPendingSpellCheck;
   nsCOMPtr<nsIDOMTreeWalker> mTreeWalker;
   nsCOMPtr<mozISpellI18NUtil> mConverter;
 
   int32_t mNumWordsInSpellSelection;
   int32_t mMaxNumWordsInSpellSelection;
 
   // How many misspellings we can add at once. This is often less than the max
   // total number of misspellings. When you have a large textarea prepopulated
@@ -142,24 +150,45 @@ private:
   // user can also have spellchecking in it.
   int32_t mMaxMisspellingsPerCheck;
 
   // we need to keep track of the current text position in the document
   // so we can spell check the old word when the user clicks around the document.
   nsCOMPtr<nsIDOMNode> mCurrentSelectionAnchorNode;
   int32_t              mCurrentSelectionOffset;
 
+  // Tracks the number of pending spell checks *and* async operations that may
+  // lead to spell checks, like updating the current dictionary.  This is
+  // necessary so that observers can know when to wait for spell check to
+  // complete.
+  int32_t mNumPendingSpellChecks;
+
+  // The number of calls to UpdateCurrentDictionary that haven't finished yet.
+  int32_t mNumPendingUpdateCurrentDictionary;
+
+  // This number is incremented each time the spell checker is disabled so that
+  // pending scheduled spell checks and UpdateCurrentDictionary calls can be
+  // ignored when they finish.
+  uint32_t mDisabledAsyncToken;
+
+  // When mPendingSpellCheck is non-null, this is the callback passed when
+  // it was initialized.
+  nsCOMPtr<InitEditorSpellCheckCallback> mPendingInitEditorSpellCheckCallback;
+
   // Set when we have spellchecked after the last edit operation. See the
   // commment at the top of the .cpp file for more info.
   bool mNeedsCheckAfterNavigation;
 
   // Set when we have a pending mozInlineSpellResume which will check
   // the whole document.
   bool mFullSpellCheckScheduled;
 
+  // Maintains state during the asynchronous UpdateCurrentDictionary call.
+  nsString mPreviousDictionary;
+
 public:
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIEDITACTIONLISTENER
   NS_DECL_NSIINLINESPELLCHECKER
   NS_DECL_NSIDOMEVENTLISTENER
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(mozInlineSpellChecker, nsIDOMEventListener)
 
@@ -223,11 +252,23 @@ public:
   nsresult RegisterEventListeners();
   nsresult UnregisterEventListeners();
   nsresult HandleNavigationEvent(bool aForceWordSpellCheck, int32_t aNewPositionOffset = 0);
 
   nsresult GetSpellCheckSelection(nsISelection ** aSpellCheckSelection);
   nsresult SaveCurrentSelectionPosition();
 
   nsresult ResumeCheck(mozInlineSpellStatus* aStatus);
+
+protected:
+
+  // called when async nsIEditorSpellCheck methods complete
+  nsresult EditorSpellCheckInited();
+  nsresult CurrentDictionaryUpdated();
+
+  // track the number of pending spell checks and async operations that may lead
+  // to spell checks, notifying observers accordingly
+  void ChangeNumPendingSpellChecks(int32_t aDelta,
+                                   nsIEditor* aEditor = nullptr);
+  void NotifyObservers(const char* aTopic, nsIEditor* aEditor);
 };
 
 #endif /* __mozinlinespellchecker_h__ */
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -375,17 +375,16 @@ nsLayoutStatics::Shutdown()
 
   NS_ShutdownEventTargetChainItemRecyclePool();
 
   HTMLInputElement::DestroyUploadLastDir();
 
   nsLayoutUtils::Shutdown();
 
   nsHyphenationManager::Shutdown();
-  nsEditorSpellCheck::ShutDown();
   nsDOMMutationObserver::Shutdown();
 
   AudioChannelService::Shutdown();
 
   ContentParent::ShutDown();
 
   nsRefreshDriver::Shutdown();