Bug 1026099 - Rework the spellchecker context menu to not use CPOWs. r=ehsan/billm/felipe
authorBlake Kaplan <mrbkap@gmail.com>
Fri, 03 Oct 2014 10:52:37 -0400
changeset 208711 91ffa2ab03aa45d35668db72612e32049d8b241b
parent 208710 1b532d05bccf46194922ec52acf91e78bcdd0a04
child 208712 2f69737e5b1c013845ec6e7446cc2c9c25458717
child 208756 43f525528c4291b5d761544d985bb3fff39d2309
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersehsan, billm, felipe
bugs1026099
milestone35.0a1
Bug 1026099 - Rework the spellchecker context menu to not use CPOWs. r=ehsan/billm/felipe This patch fixes a few nits and typos in the C++ spellchecking code as well as fixing the Finnish dictionary and multiple installed dictionaries in general (r=ehsan). It reworks the spellchecking context menu to use async message passing (in JS) and reduces, by a little, the CPOW use in the context menu code (r=felipe). Finally, the spellcheck IPDL no longer needs to be 'rpc' since we no longer nest spellchecking calls, so it can go back to being 'sync' (r=billm).
browser/base/content/content.js
browser/base/content/nsContextMenu.js
browser/base/content/tabbrowser.xml
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/moz.build
editor/composer/nsEditorSpellCheck.cpp
extensions/spellcheck/hunspell/src/PRemoteSpellcheckEngine.ipdl
extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.cpp
extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.h
extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.cpp
extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.h
extensions/spellcheck/hunspell/src/mozHunspell.cpp
extensions/spellcheck/hunspell/src/mozHunspell.h
extensions/spellcheck/src/mozSpellChecker.cpp
extensions/spellcheck/src/mozSpellChecker.h
toolkit/content/widgets/textbox.xml
toolkit/modules/InlineSpellChecker.jsm
toolkit/modules/InlineSpellCheckerContent.jsm
toolkit/modules/moz.build
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -3,16 +3,18 @@
  * 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/. */
 
 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/ContentWebRTC.jsm");
+Cu.import("resource://gre/modules/InlineSpellChecker.jsm");
+Cu.import("resource://gre/modules/InlineSpellCheckerContent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
   "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
   "resource:///modules/ContentLinkHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
@@ -95,17 +97,25 @@ if (Services.appinfo.processType == Serv
         // Don't open a context menu for plugins.
         return;
       }
 
       defaultPrevented = false;
     }
 
     if (!defaultPrevented) {
-      sendSyncMessage("contextmenu", {}, { event: event });
+      let editFlags = SpellCheckHelper.isEditable(event.target, content);
+      let spellInfo;
+      if (editFlags &
+          (SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
+        spellInfo =
+          InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
+      }
+
+      sendSyncMessage("contextmenu", { editFlags, spellInfo }, { event });
     }
   }, false);
 } else {
   addEventListener("mozUITour", function(event) {
     if (!Services.prefs.getBoolPref("browser.uitour.enabled"))
       return;
 
     let handled = UITour.onPageEvent(event);
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1,13 +1,15 @@
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
 # 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/.
 
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
 
 var gContextMenuContentData = null;
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
   this.initMenu(aXulMenu, aIsShift);
 }
 
@@ -513,21 +515,23 @@ nsContextMenu.prototype = {
       }
     }.bind(this));
   },
 
   // Set various context menu attributes based on the state of the world.
   setTarget: function (aNode, aRangeParent, aRangeOffset) {
     // If gContextMenuContentData is not null, this event was forwarded from a
     // child process, so use that information instead.
+    let editFlags;
     if (gContextMenuContentData) {
       this.isRemote = true;
       aNode = gContextMenuContentData.event.target;
       aRangeParent = gContextMenuContentData.event.rangeParent;
       aRangeOffset = gContextMenuContentData.event.rangeOffset;
+      editFlags = gContextMenuContentData.editFlags;
     } else {
       this.isRemote = false;
     }
 
     const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     if (aNode.namespaceURI == xulNS ||
         aNode.nodeType == Node.DOCUMENT_NODE ||
         this.isDisabledForEvents(aNode)) {
@@ -573,16 +577,17 @@ nsContextMenu.prototype = {
     this.focusedWindow = win;
     this.focusedElement = elt;
 
     // If this is a remote context menu event, use the information from
     // gContextMenuContentData instead.
     if (this.isRemote) {
       this.browser = gContextMenuContentData.browser;
     } else {
+      editFlags = SpellCheckHelper.isEditable(this.target, window);
       this.browser = this.target.ownerDocument.defaultView
                                   .QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIWebNavigation)
                                   .QueryInterface(Ci.nsIDocShell)
                                   .chromeEventHandler;
     }
     this.onSocial = !!this.browser.getAttribute("origin");
 
@@ -623,34 +628,29 @@ nsContextMenu.prototype = {
         } else {
           this.onVideo = true;
         }
       }
       else if (this.target instanceof HTMLAudioElement) {
         this.onAudio = true;
         this.mediaURL = this.target.currentSrc || this.target.src;
       }
-      else if (this.target instanceof HTMLInputElement ) {
-        this.onTextInput = this.isTargetATextBox(this.target);
-        // Allow spellchecking UI on all text and search inputs.
-        if (this.onTextInput && ! this.target.readOnly &&
-            (this.target.type == "text" || this.target.type == "search")) {
-          this.onEditableArea = true;
-          InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
-          InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+      else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
+        this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
+        this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
+        if (this.onEditableArea) {
+          if (gContextMenuContentData) {
+            InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
+          }
+          else {
+            InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+            InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+          }
         }
-        this.onKeywordField = this.isTargetAKeywordField(this.target);
-      }
-      else if (this.target instanceof HTMLTextAreaElement) {
-        this.onTextInput = true;
-        if (!this.target.readOnly) {
-          this.onEditableArea = true;
-          InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
-          InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
-        }
+        this.onKeywordField = (editFlags & SpellCheckHelper.KEYWORD);
       }
       else if (this.target instanceof HTMLHtmlElement) {
         var bodyElt = this.target.ownerDocument.body;
         if (bodyElt) {
           let computedURL;
           try {
             computedURL = this.getComputedURL(bodyElt, "background-image");
             this._hasMultipleBGImages = false;
@@ -743,51 +743,45 @@ nsContextMenu.prototype = {
 
       if (this.target.ownerDocument.isSrcdocDocument) {
           this.inSrcdocFrame = true;
       }
     }
 
     // if the document is editable, show context menu like in text inputs
     if (!this.onEditableArea) {
-      win = this.target.ownerDocument.defaultView;
-      if (win) {
-        var isEditable = false;
-        try {
-          var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                                  .getInterface(Ci.nsIWebNavigation)
-                                  .QueryInterface(Ci.nsIInterfaceRequestor)
-                                  .getInterface(Ci.nsIEditingSession);
-          if (editingSession.windowIsEditable(win) &&
-              this.getComputedStyle(this.target, "-moz-user-modify") == "read-write") {
-            isEditable = true;
-          }
-        }
-        catch(ex) {
-          // If someone built with composer disabled, we can't get an editing session.
+      if (editFlags & SpellCheckHelper.CONTENTEDITABLE) {
+        // If this.onEditableArea is false but editFlags is CONTENTEDITABLE, then
+        // the document itself must be editable.
+        this.onTextInput       = true;
+        this.onKeywordField    = false;
+        this.onImage           = false;
+        this.onLoadedImage     = false;
+        this.onCompletedImage  = false;
+        this.onMathML          = false;
+        this.inFrame           = false;
+        this.inSrcdocFrame     = false;
+        this.hasBGImage        = false;
+        this.isDesignMode      = true;
+        this.onEditableArea = true;
+        if (gContextMenuContentData) {
+          InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
         }
-
-        if (isEditable) {
-          this.onTextInput       = true;
-          this.onKeywordField    = false;
-          this.onImage           = false;
-          this.onLoadedImage     = false;
-          this.onCompletedImage  = false;
-          this.onMathML          = false;
-          this.inFrame           = false;
-          this.inSrcdocFrame     = false;
-          this.hasBGImage        = false;
-          this.isDesignMode      = true;
-          this.onEditableArea = true;
-          InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win));
-          var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
+        else {
+          var targetWin = this.target.ownerDocument.defaultView;
+          var editingSession = targetWin.QueryInterface(Ci.nsIInterfaceRequestor)
+                                        .getInterface(Ci.nsIWebNavigation)
+                                        .QueryInterface(Ci.nsIInterfaceRequestor)
+                                        .getInterface(Ci.nsIEditingSession);
+          InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
-          this.showItem("spell-check-enabled", canSpell);
-          this.showItem("spell-separator", canSpell);
         }
+        var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
+        this.showItem("spell-check-enabled", canSpell);
+        this.showItem("spell-separator", canSpell);
       }
     }
   },
 
   // Returns the computed style attribute for the given element.
   getComputedStyle: function(aElem, aProp) {
     return aElem.ownerDocument
                 .defaultView
@@ -1497,40 +1491,16 @@ nsContextMenu.prototype = {
 
   isTargetATextBox: function(node) {
     if (node instanceof HTMLInputElement)
       return node.mozIsTextField(false);
 
     return (node instanceof HTMLTextAreaElement);
   },
 
-  isTargetAKeywordField: function(aNode) {
-    if (!(aNode instanceof HTMLInputElement))
-      return false;
-
-    var form = aNode.form;
-    if (!form || aNode.type == "password")
-      return false;
-
-    var method = form.method.toUpperCase();
-
-    // These are the following types of forms we can create keywords for:
-    //
-    // method   encoding type       can create keyword
-    // GET      *                                 YES
-    //          *                                 YES
-    // POST                                       YES
-    // POST     application/x-www-form-urlencoded YES
-    // POST     text/plain                        NO (a little tricky to do)
-    // POST     multipart/form-data               NO
-    // POST     everything else                   YES
-    return (method == "GET" || method == "") ||
-           (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
-  },
-
   // Determines whether or not the separator with the specified ID should be
   // shown or not by determining if there are any non-hidden items between it
   // and the previous separator.
   shouldShowSeparator: function (aSeparatorID) {
     var separator = document.getElementById(aSeparatorID);
     if (separator) {
       var sibling = separator.previousSibling;
       while (sibling && sibling.localName != "menuseparator") {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3044,18 +3044,23 @@
 
               let tab = this._getTabForBrowser(browser);
               if (tab) {
                 this.removeTab(tab);
               }
               break;
             }
             case "contextmenu": {
+              let spellInfo = aMessage.data.spellInfo;
+              if (spellInfo)
+                spellInfo.target = aMessage.target.messageManager;
               gContextMenuContentData = { event: aMessage.objects.event,
-                                          browser: browser };
+                                          browser: browser,
+                                          editFlags: aMessage.data.editFlags,
+                                          spellInfo: spellInfo };
               let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
               let event = gContextMenuContentData.event;
               let pos = browser.mapScreenCoordinatesFromContent(event.screenX, event.screenY);
               popup.openPopupAtScreen(pos.x, pos.y, true);
               break;
             }
             case "DOMWebNotificationClicked": {
               let tab = this._getTabForBrowser(browser);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -48,16 +48,17 @@
 #include "nsDirectoryServiceDefs.h"
 #elif defined(XP_LINUX)
 #include "mozilla/Sandbox.h"
 #endif
 #endif
 
 #include "mozilla/unused.h"
 
+#include "mozInlineSpellChecker.h"
 #include "nsIConsoleListener.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
@@ -72,16 +73,17 @@
 #include "nsJSEnvironment.h"
 #include "SandboxHal.h"
 #include "nsDebugImpl.h"
 #include "nsHashPropertyBag.h"
 #include "nsLayoutStylesheetCache.h"
 #include "nsIJSRuntimeService.h"
 #include "nsThreadManager.h"
 #include "nsAnonymousTemporaryFile.h"
+#include "nsISpellChecker.h"
 
 #include "IHistory.h"
 #include "nsNetUtil.h"
 
 #include "base/message_loop.h"
 #include "base/process_util.h"
 #include "base/task.h"
 
@@ -688,17 +690,17 @@ ContentChild::InitXPCOM()
         return;
     }
 
     mConsoleListener = new ConsoleListener(this);
     if (NS_FAILED(svc->RegisterListener(mConsoleListener)))
         NS_WARNING("Couldn't register console listener for child process");
 
     bool isOffline;
-    SendGetXPCOMProcessAttributes(&isOffline);
+    SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries);
     RecvSetOffline(isOffline);
 
     DebugOnly<FileUpdateDispatcher*> observer = FileUpdateDispatcher::GetSingleton();
     NS_ASSERTION(observer, "FileUpdateDispatcher is null");
 
     // This object is held alive by the observer service.
     nsRefPtr<SystemMessageHandledObserver> sysMsgObserver =
         new SystemMessageHandledObserver();
@@ -1120,16 +1122,22 @@ ContentChild::RecvPBrowserConstructor(PB
         mIsForApp = aIsForApp;
         mIsForBrowser = aIsForBrowser;
         InitProcessAttributes();
     }
 
     return true;
 }
 
+void
+ContentChild::GetAvailableDictionaries(InfallibleTArray<nsString>& aDictionaries)
+{
+    aDictionaries = mAvailableDictionaries;
+}
+
 PFileDescriptorSetChild*
 ContentChild::AllocPFileDescriptorSetChild(const FileDescriptor& aFD)
 {
     return new FileDescriptorSetChild(aFD);
 }
 
 bool
 ContentChild::DeallocPFileDescriptorSetChild(PFileDescriptorSetChild* aActor)
@@ -1148,17 +1156,17 @@ PBlobChild*
 ContentChild::AllocPBlobChild(const BlobConstructorParams& aParams)
 {
     return nsIContentChild::AllocPBlobChild(aParams);
 }
 
 mozilla::PRemoteSpellcheckEngineChild *
 ContentChild::AllocPRemoteSpellcheckEngineChild()
 {
-    NS_NOTREACHED("Default Constructor for PRemoteSpellcheckEngineChilf should never be called");
+    NS_NOTREACHED("Default Constructor for PRemoteSpellcheckEngineChild should never be called");
     return nullptr;
 }
 
 bool
 ContentChild::DeallocPRemoteSpellcheckEngineChild(PRemoteSpellcheckEngineChild *child)
 {
     delete child;
     return true;
@@ -1738,16 +1746,24 @@ ContentChild::RecvGeolocationUpdate(cons
         return true;
     }
     nsCOMPtr<nsIDOMGeoPosition> position = somewhere;
     gs->Update(position);
     return true;
 }
 
 bool
+ContentChild::RecvUpdateDictionaryList(const InfallibleTArray<nsString>& aDictionaries)
+{
+    mAvailableDictionaries = aDictionaries;
+    mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking();
+    return true;
+}
+
+bool
 ContentChild::RecvAddPermission(const IPC::Permission& permission)
 {
 #if MOZ_PERMISSIONS
     nsCOMPtr<nsIPermissionManager> permissionManagerIface =
         services::GetPermissionManager();
     nsPermissionManager* permissionManager =
         static_cast<nsPermissionManager*>(permissionManagerIface.get());
     NS_ABORT_IF_FALSE(permissionManager,
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -288,16 +288,18 @@ public:
 
     virtual bool RecvAsyncMessage(const nsString& aMsg,
                                   const ClonedMessageData& aData,
                                   const InfallibleTArray<CpowEntry>& aCpows,
                                   const IPC::Principal& aPrincipal) MOZ_OVERRIDE;
 
     virtual bool RecvGeolocationUpdate(const GeoPosition& somewhere) MOZ_OVERRIDE;
 
+    virtual bool RecvUpdateDictionaryList(const InfallibleTArray<nsString>& aDictionaries) MOZ_OVERRIDE;
+
     virtual bool RecvAddPermission(const IPC::Permission& permission) MOZ_OVERRIDE;
 
     virtual bool RecvScreenSizeChanged(const gfxIntSize &size) MOZ_OVERRIDE;
 
     virtual bool RecvFlushMemory(const nsString& reason) MOZ_OVERRIDE;
 
     virtual bool RecvActivateA11y() MOZ_OVERRIDE;
 
@@ -376,32 +378,36 @@ public:
 
     virtual bool RecvPBrowserConstructor(PBrowserChild* aCctor,
                                          const IPCTabContext& aContext,
                                          const uint32_t& aChromeFlags,
                                          const uint64_t& aID,
                                          const bool& aIsForApp,
                                          const bool& aIsForBrowser) MOZ_OVERRIDE;
 
+    void GetAvailableDictionaries(InfallibleTArray<nsString>& aDictionaries);
+
 private:
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
     virtual void ProcessingError(Result what) MOZ_OVERRIDE;
 
     /**
      * Exit *now*.  Do not shut down XPCOM, do not pass Go, do not run
      * static destructors, do not collect $200.
      */
     MOZ_NORETURN void QuickExit();
 
     InfallibleTArray<nsAutoPtr<AlertObserver> > mAlertObservers;
     nsRefPtr<ConsoleListener> mConsoleListener;
 
     nsTHashtable<nsPtrHashKey<nsIObserver>> mIdleObservers;
 
+    InfallibleTArray<nsString> mAvailableDictionaries;
+
     /**
      * An ID unique to the process containing our corresponding
      * content parent.
      *
      * We expect our content parent to set this ID immediately after opening a
      * channel to us.
      */
     uint64_t mID;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -96,16 +96,17 @@
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIPresShell.h"
 #include "nsIScriptError.h"
 #include "nsISiteSecurityService.h"
+#include "nsISpellChecker.h"
 #include "nsIStyleSheet.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIURIFixup.h"
 #include "nsIWindowWatcher.h"
 #include "nsIXULRuntime.h"
 #include "nsMemoryInfoDumper.h"
 #include "nsMemoryReporterManager.h"
 #include "nsServiceManagerUtils.h"
@@ -2491,17 +2492,18 @@ ContentParent::RecvAddNewProcess(const u
                                 sNuwaPrefUpdates->Length() : 0;
     // Resend pref updates to the forked child.
     for (int i = 0; i < numNuwaPrefUpdates; i++) {
         content->SendPreferenceUpdate(sNuwaPrefUpdates->ElementAt(i));
     }
 
     // Update offline settings.
     bool isOffline;
-    RecvGetXPCOMProcessAttributes(&isOffline);
+    InfallibleTArray<nsString> unusedDictionaries;
+    RecvGetXPCOMProcessAttributes(&isOffline, &unusedDictionaries);
     content->SendSetOffline(isOffline);
 
     PreallocatedProcessManager::PublishSpareProcess(content);
     return true;
 #else
     NS_ERROR("ContentParent::RecvAddNewProcess() not implemented!");
     return false;
 #endif
@@ -2739,22 +2741,28 @@ ContentParent::RecvGetProcessAttributes(
     *aId = mChildID;
     *aIsForApp = IsForApp();
     *aIsForBrowser = mIsForBrowser;
 
     return true;
 }
 
 bool
-ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline)
+ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline,
+                                             InfallibleTArray<nsString>* dictionaries)
 {
     nsCOMPtr<nsIIOService> io(do_GetIOService());
-    NS_ASSERTION(io, "No IO service?");
+    MOZ_ASSERT(io, "No IO service?");
     DebugOnly<nsresult> rv = io->GetOffline(aIsOffline);
-    NS_ASSERTION(NS_SUCCEEDED(rv), "Failed getting offline?");
+    MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
+
+    nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID));
+    MOZ_ASSERT(spellChecker, "No spell checker?");
+
+    spellChecker->GetDictionaryList(dictionaries);
 
     return true;
 }
 
 mozilla::jsipc::PJavaScriptParent *
 ContentParent::AllocPJavaScriptParent()
 {
     MOZ_ASSERT(!ManagedPJavaScriptParent().Length());
@@ -4048,16 +4056,33 @@ ContentParent::IgnoreIPCPrincipal()
   if (!sDidAddVarCache) {
     sDidAddVarCache = true;
     Preferences::AddBoolVarCache(&sIgnoreIPCPrincipal,
                                  "dom.testing.ignore_ipc_principal", false);
   }
   return sIgnoreIPCPrincipal;
 }
 
+void
+ContentParent::NotifyUpdatedDictionaries()
+{
+    nsAutoTArray<ContentParent*, 8> processes;
+    GetAll(processes);
+
+    nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID));
+    MOZ_ASSERT(spellChecker, "No spell checker?");
+
+    InfallibleTArray<nsString> dictionaries;
+    spellChecker->GetDictionaryList(&dictionaries);
+
+    for (size_t i = 0; i < processes.Length(); ++i) {
+        unused << processes[i]->SendUpdateDictionaryList(dictionaries);
+    }
+}
+
 } // namespace dom
 } // namespace mozilla
 
 NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
 
 NS_IMETHODIMP
 ParentIdleListener::Observe(nsISupports*, const char* aTopic, const char16_t* aData) {
     mozilla::unused << mParent->SendNotifyIdleObserver(mObserver,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -134,16 +134,18 @@ public:
                        Element* aFrameElement,
                        ContentParent* aOpenerContentParent);
 
     static void GetAll(nsTArray<ContentParent*>& aArray);
     static void GetAllEvenIfDead(nsTArray<ContentParent*>& aArray);
 
     static bool IgnoreIPCPrincipal();
 
+    static void NotifyUpdatedDictionaries();
+
     virtual bool RecvCreateChildProcess(const IPCTabContext& aContext,
                                         const hal::ProcessPriority& aPriority,
                                         uint64_t* aId,
                                         bool* aIsForApp,
                                         bool* aIsForBrowser) MOZ_OVERRIDE;
     virtual bool AnswerBridgeToChildProcess(const uint64_t& id) MOZ_OVERRIDE;
 
     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
@@ -405,17 +407,19 @@ private:
                                      base::ProcessId aOtherProcess) MOZ_OVERRIDE;
     PBackgroundParent*
     AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess)
                            MOZ_OVERRIDE;
 
     virtual bool RecvGetProcessAttributes(uint64_t* aId,
                                           bool* aIsForApp,
                                           bool* aIsForBrowser) MOZ_OVERRIDE;
-    virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline) MOZ_OVERRIDE;
+    virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline,
+                                               InfallibleTArray<nsString>* dictionaries)
+        MOZ_OVERRIDE;
 
     virtual bool DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) MOZ_OVERRIDE;
 
     virtual bool DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) MOZ_OVERRIDE;
     virtual PBrowserParent* AllocPBrowserParent(const IPCTabContext& aContext,
                                                 const uint32_t& aChromeFlags,
                                                 const uint64_t& aId,
                                                 const bool& aIsForApp,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -428,26 +428,28 @@ child:
     async SystemMemoryAvailable(uint64_t getterId, uint32_t memoryAvailable);
 
     PreferenceUpdate(PrefSetting pref);
 
     NotifyAlertsObserver(nsCString topic, nsString data);
 
     GeolocationUpdate(GeoPosition somewhere);
 
+    UpdateDictionaryList(nsString[] dictionaries);
+
     // nsIPermissionManager messages
     AddPermission(Permission permission);
 
     ScreenSizeChanged(gfxIntSize size);
 
     FlushMemory(nsString reason);
 
     GarbageCollect();
     CycleCollect();
-    
+
     /**
      * Start accessibility engine in content process.
      */
     ActivateA11y();
 
     AppInfo(nsCString version, nsCString buildID, nsCString name, nsCString UAName,
             nsCString ID, nsCString vendor);
 
@@ -499,17 +501,17 @@ parent:
      * |isForBrowser|, we're loading <browser>.  When |!isForApp &&
      * !isForBrowser|, we're probably loading <xul:browser remote>.
      *
      * Keep the return values in sync with PBrowser()!
      */
     sync GetProcessAttributes()
         returns (uint64_t id, bool isForApp, bool isForBrowser);
     sync GetXPCOMProcessAttributes()
-        returns (bool isOffline);
+        returns (bool isOffline, nsString[] dictionaries);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority)
         returns (uint64_t id, bool isForApp, bool isForBrowser);
     intr BridgeToChildProcess(uint64_t id);
 
     async PJavaScript();
 
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -104,17 +104,19 @@ LOCAL_INCLUDES += [
     '/dom/bluetooth',
     '/dom/bluetooth/ipc',
     '/dom/devicestorage',
     '/dom/filesystem',
     '/dom/fmradio/ipc',
     '/dom/geolocation',
     '/dom/mobilemessage/ipc',
     '/dom/storage',
+    '/editor/libeditor',
     '/extensions/cookie',
+    '/extensions/spellcheck/src',
     '/hal/sandbox',
     '/js/ipc',
     '/layout/base',
     '/netwerk/base/src',
     '/toolkit/xre',
     '/uriloader/exthandler',
     '/widget/xpwidgets',
     '/xpcom/base',
--- a/editor/composer/nsEditorSpellCheck.cpp
+++ b/editor/composer/nsEditorSpellCheck.cpp
@@ -1,9 +1,10 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 sw=2 tw=80: */
 /* 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 <stdlib.h>                     // for getenv
 
 #include "mozilla/Attributes.h"         // for MOZ_FINAL
 #include "mozilla/Preferences.h"        // for Preferences
@@ -41,26 +42,26 @@
 #include "nsServiceManagerUtils.h"      // for do_GetService
 #include "nsString.h"                   // for nsAutoString, nsString, etc
 #include "nsStringFwd.h"                // for nsAFlatString
 #include "nsStyleUtil.h"                // for nsStyleUtil
 #include "nsXULAppAPI.h"                // for XRE_GetProcessType
 
 using namespace mozilla;
 
-class UpdateDictionnaryHolder {
+class UpdateDictionaryHolder {
   private:
     nsEditorSpellCheck* mSpellCheck;
   public:
-    explicit UpdateDictionnaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
+    explicit UpdateDictionaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
       if (mSpellCheck) {
         mSpellCheck->BeginUpdateDictionary();
       }
     }
-    ~UpdateDictionnaryHolder() {
+    ~UpdateDictionaryHolder() {
       if (mSpellCheck) {
         mSpellCheck->EndUpdateDictionary();
       }
     }
 };
 
 #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
 
@@ -694,63 +695,53 @@ nsEditorSpellCheck::UpdateCurrentDiction
   } 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);
 
-  DictionaryFetcher* fetcher = new DictionaryFetcher(this, aCallback,
-                                                     mDictionaryFetcherGroup);
+  nsRefPtr<DictionaryFetcher> fetcher =
+    new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup);
   rootContent->GetLang(fetcher->mRootContentLang);
   nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
   NS_ENSURE_STATE(doc);
   doc->GetContentLanguage(fetcher->mRootDocContentLang);
 
-  if (XRE_GetProcessType() == GeckoProcessType_Content) {
-    // Content prefs don't work in E10S (Bug 1027898) pretend that we
-    // didn't have any & trigger the asynchrous completion.
-    nsCOMPtr<nsIRunnable> runnable =
-      NS_NewRunnableMethodWithArg<uint16_t>(fetcher, &DictionaryFetcher::HandleCompletion, 0);
-    NS_DispatchToMainThread(runnable);
-  } else {
-    rv = fetcher->Fetch(mEditor);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  rv = fetcher->Fetch(mEditor);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
 {
+  MOZ_ASSERT(aFetcher);
   nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
 
-  nsresult rv = NS_OK;
-
   // 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);
+  UpdateDictionaryHolder 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;
-  dictName.Assign(aFetcher->mDictionary);
+  nsAutoString dictName(aFetcher->mDictionary);
   if (!dictName.IsEmpty()) {
-    if (NS_FAILED(SetCurrentDictionary(dictName))) { 
+    if (NS_FAILED(SetCurrentDictionary(dictName))) {
       // may be dictionary was uninstalled ?
       ClearCurrentDictionary(mEditor);
     }
     return NS_OK;
   }
 
   if (mPreferredLang.IsEmpty()) {
     mPreferredLang.Assign(aFetcher->mRootDocContentLang);
@@ -768,18 +759,18 @@ nsEditorSpellCheck::DictionaryFetched(Di
   int32_t underScore = preferedDict.FindChar('_');
   if (underScore != -1) {
     preferedDict.Replace(underScore, 1, '-');
   }
   if (dictName.IsEmpty()) {
     dictName.Assign(preferedDict);
   }
 
-  if (dictName.IsEmpty())
-  {
+  nsresult rv = NS_OK;
+  if (dictName.IsEmpty()) {
     // Prefs didn't give us a dictionary name, so just get the current
     // locale and use that as the default dictionary name!
 
     nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
       mozilla::services::GetXULChromeRegistryService();
 
     if (packageRegistry) {
       nsAutoCString utf8DictName;
--- a/extensions/spellcheck/hunspell/src/PRemoteSpellcheckEngine.ipdl
+++ b/extensions/spellcheck/hunspell/src/PRemoteSpellcheckEngine.ipdl
@@ -1,22 +1,22 @@
 /* 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 protocol PContent;
 
 namespace mozilla {
 
-rpc protocol PRemoteSpellcheckEngine {
+sync protocol PRemoteSpellcheckEngine {
   manager PContent;
 
 parent:
   __delete__();
 
-  rpc Check(nsString aWord) returns (bool aIsMisspelled);
+  sync Check(nsString aWord) returns (bool aIsMisspelled);
 
-  rpc CheckAndSuggest(nsString aWord) returns (bool aIsMisspelled, nsString[] aSuggestions);
+  sync CheckAndSuggest(nsString aWord) returns (bool aIsMisspelled, nsString[] aSuggestions);
 
-  rpc SetDictionary(nsString aDictionary) returns (bool success);
+  sync SetDictionary(nsString aDictionary) returns (bool success);
 };
 
 } // namespace mozilla
--- a/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.cpp
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.cpp
@@ -1,21 +1,21 @@
 /* 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 "RemoteSpellCheckEngineChild.h"
 
 namespace mozilla {
+
 RemoteSpellcheckEngineChild::RemoteSpellcheckEngineChild(mozSpellChecker *aOwner)
-  :mOwner(aOwner)
+  : mOwner(aOwner)
 {
 }
 
 RemoteSpellcheckEngineChild::~RemoteSpellcheckEngineChild()
 {
   // null out the owner's SpellcheckEngineChild to prevent state corruption
   // during shutdown
   mOwner->DeleteRemoteEngine();
-
 }
 
 } //namespace mozilla
--- a/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.h
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.h
@@ -6,21 +6,22 @@
 #define RemoteSpellcheckEngineChild_h_
 
 #include "mozilla/PRemoteSpellcheckEngineChild.h"
 #include "mozSpellChecker.h"
 
 class mozSpellChecker;
 
 namespace mozilla {
+
 class RemoteSpellcheckEngineChild : public mozilla::PRemoteSpellcheckEngineChild
 {
 public:
   explicit RemoteSpellcheckEngineChild(mozSpellChecker *aOwner);
-  ~RemoteSpellcheckEngineChild();
+  virtual ~RemoteSpellcheckEngineChild();
 
 private:
   mozSpellChecker *mOwner;
 };
 
 } //namespace mozilla
 
 #endif // RemoteSpellcheckEngineChild_h_
--- a/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.cpp
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.cpp
@@ -1,70 +1,63 @@
+/* vim: set ts=2 sw=2 sts=2 tw=80: */
 /* 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 "RemoteSpellCheckEngineParent.h"
-#include "mozISpellCheckingEngine.h"
+#include "nsISpellChecker.h"
 #include "nsServiceManagerUtils.h"
 
-#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
-
 namespace mozilla {
 
 RemoteSpellcheckEngineParent::RemoteSpellcheckEngineParent()
 {
-  mEngine = do_GetService(DEFAULT_SPELL_CHECKER);
+  mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID);
 }
 
 RemoteSpellcheckEngineParent::~RemoteSpellcheckEngineParent()
 {
 }
 
 bool
-RemoteSpellcheckEngineParent::AnswerSetDictionary(
+RemoteSpellcheckEngineParent::RecvSetDictionary(
   const nsString& aDictionary,
   bool* success)
 {
-  nsresult rv = mEngine->SetDictionary(aDictionary.get());
+  nsresult rv = mSpellChecker->SetCurrentDictionary(aDictionary);
   *success = NS_SUCCEEDED(rv);
   return true;
 }
 
 bool
-RemoteSpellcheckEngineParent::AnswerCheck(
+RemoteSpellcheckEngineParent::RecvCheck(
   const nsString& aWord,
   bool* aIsMisspelled)
 {
-  bool isCorrect = true;
-  mEngine->Check(aWord.get(), &isCorrect);
-  *aIsMisspelled = !isCorrect;
+  nsresult rv = mSpellChecker->CheckWord(aWord, aIsMisspelled, nullptr);
+
+  // If CheckWord failed, we can't tell whether the word is correctly spelled.
+  if (NS_FAILED(rv))
+    *aIsMisspelled = false;
   return true;
 }
 
 bool
-RemoteSpellcheckEngineParent::AnswerCheckAndSuggest(
+RemoteSpellcheckEngineParent::RecvCheckAndSuggest(
   const nsString& aWord,
   bool* aIsMisspelled,
   InfallibleTArray<nsString>* aSuggestions)
 {
-  bool isCorrect = true;
-  mEngine->Check(aWord.get(), &isCorrect);
-  *aIsMisspelled = !isCorrect;
-  if (!isCorrect) {
-    char16_t **suggestions;
-    uint32_t count = 0;
-    mEngine->Suggest(aWord.get(), &suggestions, &count);
-
-    for (uint32_t i=0; i<count; i++) {
-      aSuggestions->AppendElement(nsDependentString(suggestions[i]));
-    }
+  nsresult rv = mSpellChecker->CheckWord(aWord, aIsMisspelled, aSuggestions);
+  if (NS_FAILED(rv)) {
+    aSuggestions->Clear();
+    *aIsMisspelled = false;
   }
   return true;
 }
 
 void
 RemoteSpellcheckEngineParent::ActorDestroy(ActorDestroyReason aWhy)
 {
 }
 
 } // namespace mozilla
-
--- a/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.h
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.h
@@ -1,38 +1,38 @@
 /* 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/. */
 #ifndef RemoteSpellcheckEngineParent_h_
 #define RemoteSpellcheckEngineParent_h_
 
-#include "mozISpellCheckingEngine.h"
 #include "mozilla/PRemoteSpellcheckEngineParent.h"
 #include "nsCOMPtr.h"
 
+class nsISpellChecker;
+
 namespace mozilla {
 
-class RemoteSpellcheckEngineParent : public mozilla::PRemoteSpellcheckEngineParent {
-
+class RemoteSpellcheckEngineParent : public PRemoteSpellcheckEngineParent
+{
 public:
   RemoteSpellcheckEngineParent();
 
-  ~RemoteSpellcheckEngineParent();
+  virtual ~RemoteSpellcheckEngineParent();
 
-  virtual void ActorDestroy(ActorDestroyReason aWhy);
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
-  bool AnswerSetDictionary(const nsString& aDictionary, bool* success);
+  virtual bool RecvSetDictionary(const nsString& aDictionary,
+                                   bool* success) MOZ_OVERRIDE;
 
-  bool AnswerCheck( const nsString& aWord, bool* aIsMisspelled);
+  virtual bool RecvCheck(const nsString& aWord, bool* aIsMisspelled) MOZ_OVERRIDE;
 
-  bool AnswerCheckAndSuggest(
-            const nsString& aWord,
-            bool* aIsMisspelled,
-            InfallibleTArray<nsString>* aSuggestions);
-
-
+  virtual bool RecvCheckAndSuggest(const nsString& aWord,
+                                     bool* aIsMisspelled,
+                                     InfallibleTArray<nsString>* aSuggestions)
+      MOZ_OVERRIDE;
 
 private:
-  nsCOMPtr<mozISpellCheckingEngine> mEngine;
+  nsCOMPtr<nsISpellChecker> mSpellChecker;
 };
 
 }
 #endif
--- a/extensions/spellcheck/hunspell/src/mozHunspell.cpp
+++ b/extensions/spellcheck/hunspell/src/mozHunspell.cpp
@@ -71,17 +71,19 @@
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
 #include "mozInlineSpellChecker.h"
 #include "mozilla/Services.h"
 #include <stdlib.h>
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/ContentParent.h"
 
+using mozilla::dom::ContentParent;
 using mozilla::dom::EncodingUtils;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozHunspell)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozHunspell)
 
 NS_INTERFACE_MAP_BEGIN(mozHunspell)
   NS_INTERFACE_MAP_ENTRY(mozISpellCheckingEngine)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
@@ -108,17 +110,17 @@ mozHunspell::mozHunspell()
   MOZ_ASSERT(!hasRun);
   hasRun = true;
 #endif
 }
 
 nsresult
 mozHunspell::Init()
 {
-  LoadDictionaryList();
+  LoadDictionaryList(false);
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->AddObserver(this, "profile-do-change", true);
     obs->AddObserver(this, "profile-after-change", true);
   }
 
   mozilla::RegisterWeakMemoryReporter(this);
@@ -339,17 +341,17 @@ NS_IMETHODIMP mozHunspell::GetDictionary
 
   *aDictionaries = ans.dics;
   *aCount = ans.count;
 
   return NS_OK;
 }
 
 void
-mozHunspell::LoadDictionaryList()
+mozHunspell::LoadDictionaryList(bool aNotifyChildProcesses)
 {
   mDictionaries.Clear();
 
   nsresult rv;
 
   nsCOMPtr<nsIProperties> dirSvc =
     do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
   if (!dirSvc)
@@ -419,16 +421,20 @@ mozHunspell::LoadDictionaryList()
   for (int32_t i = 0; i < mDynamicDirectories.Count(); i++) {
     LoadDictionariesFromDir(mDynamicDirectories[i]);
   }
 
   // Now we have finished updating the list of dictionaries, update the current
   // dictionary and any editors which may use it.
   mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking();
 
+  if (aNotifyChildProcesses) {
+    ContentParent::NotifyUpdatedDictionaries();
+  }
+
   // Check if the current dictionary is still available.
   // If not, try to replace it with another dictionary of the same language.
   if (!mDictionary.IsEmpty()) {
     rv = SetDictionary(mDictionary.get());
     if (NS_SUCCEEDED(rv))
       return;
   }
 
@@ -584,28 +590,28 @@ NS_IMETHODIMP mozHunspell::Suggest(const
 NS_IMETHODIMP
 mozHunspell::Observe(nsISupports* aSubj, const char *aTopic,
                     const char16_t *aData)
 {
   NS_ASSERTION(!strcmp(aTopic, "profile-do-change")
                || !strcmp(aTopic, "profile-after-change"),
                "Unexpected observer topic");
 
-  LoadDictionaryList();
+  LoadDictionaryList(false);
 
   return NS_OK;
 }
 
 /* void addDirectory(in nsIFile dir); */
 NS_IMETHODIMP mozHunspell::AddDirectory(nsIFile *aDir)
 {
   mDynamicDirectories.AppendObject(aDir);
-  LoadDictionaryList();
+  LoadDictionaryList(true);
   return NS_OK;
 }
 
 /* void removeDirectory(in nsIFile dir); */
 NS_IMETHODIMP mozHunspell::RemoveDirectory(nsIFile *aDir)
 {
   mDynamicDirectories.RemoveObject(aDir);
-  LoadDictionaryList();
+  LoadDictionaryList(true);
   return NS_OK;
 }
--- a/extensions/spellcheck/hunspell/src/mozHunspell.h
+++ b/extensions/spellcheck/hunspell/src/mozHunspell.h
@@ -91,17 +91,17 @@ public:
   NS_DECL_MOZISPELLCHECKINGENGINE
   NS_DECL_NSIOBSERVER
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(mozHunspell, mozISpellCheckingEngine)
 
   mozHunspell();
 
   nsresult Init();
 
-  void LoadDictionaryList();
+  void LoadDictionaryList(bool aNotifyChildProcesses);
 
   // helper method for converting a word to the charset of the dictionary
   nsresult ConvertCharset(const char16_t* aStr, char ** aDst);
 
   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
                             nsISupports* aData, bool aAnonymize)
   {
     return MOZ_COLLECT_REPORT(
--- a/extensions/spellcheck/src/mozSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozSpellChecker.cpp
@@ -1,8 +1,9 @@
+/* vim: set ts=2 sts=2 sw=2 tw=80: */
 /* 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 "mozSpellChecker.h"
 #include "nsIServiceManager.h"
 #include "mozISpellI18NManager.h"
@@ -29,48 +30,51 @@ NS_INTERFACE_MAP_BEGIN(mozSpellChecker)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION(mozSpellChecker,
                          mTsDoc,
                          mPersonalDictionary)
 
 mozSpellChecker::mozSpellChecker()
+  : mEngine(nullptr)
 {
 }
 
 mozSpellChecker::~mozSpellChecker()
 {
-  if(mPersonalDictionary){
+  if (mPersonalDictionary) {
     //    mPersonalDictionary->Save();
     mPersonalDictionary->EndSession();
   }
   mSpellCheckingEngine = nullptr;
   mPersonalDictionary = nullptr;
 
-  if(XRE_GetProcessType() == GeckoProcessType_Content) {
+  if (mEngine) {
+    MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
     mEngine->Send__delete__(mEngine);
+    MOZ_ASSERT(!mEngine);
   }
 }
 
-nsresult 
+nsresult
 mozSpellChecker::Init()
 {
-  mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
-  
   mSpellCheckingEngine = nullptr;
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
     MOZ_ASSERT(contentChild);
     mEngine = new RemoteSpellcheckEngineChild(this);
     contentChild->SendPRemoteSpellcheckEngineConstructor(mEngine);
+  } else {
+    mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
   }
 
   return NS_OK;
-} 
+}
 
 NS_IMETHODIMP 
 mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc)
 {
   mTsDoc = aDoc;
   mFromStart = aFromStartofDoc;
   return NS_OK;
 }
@@ -125,19 +129,19 @@ mozSpellChecker::CheckWord(const nsAStri
 {
   nsresult result;
   bool correct;
 
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     nsString wordwrapped = nsString(aWord);
     bool rv;
     if (aSuggestions) {
-      rv = mEngine->CallCheckAndSuggest(wordwrapped, aIsMisspelled, aSuggestions);
+      rv = mEngine->SendCheckAndSuggest(wordwrapped, aIsMisspelled, aSuggestions);
     } else {
-      rv = mEngine->CallCheck(wordwrapped, aIsMisspelled);
+      rv = mEngine->SendCheck(wordwrapped, aIsMisspelled);
     }
     return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
   if(!mSpellCheckingEngine) {
     return NS_ERROR_NULL_POINTER;
   }
   *aIsMisspelled = false;
@@ -297,19 +301,25 @@ mozSpellChecker::GetPersonalDictionary(n
   nsAutoString word;
   while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) {
     words->GetNext(word);
     aWordList->AppendElement(word);
   }
   return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList)
 {
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    ContentChild *child = ContentChild::GetSingleton();
+    child->GetAvailableDictionaries(*aDictionaryList);
+    return NS_OK;
+  }
+
   nsresult rv;
 
   // For catching duplicates
   nsTHashtable<nsStringHashKey> dictionaries;
 
   nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
   rv = GetEngineList(&spellCheckingEngines);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -339,38 +349,49 @@ mozSpellChecker::GetDictionaryList(nsTAr
     }
 
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
 {
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    aDictionary = mCurrentDictionary;
+    return NS_OK;
+  }
+
   if (!mSpellCheckingEngine) {
     aDictionary.Truncate();
     return NS_OK;
   }
 
   nsXPIDLString dictname;
   mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
   aDictionary = dictname;
   return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
 {
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     nsString wrappedDict = nsString(aDictionary);
     bool isSuccess;
-    mEngine->CallSetDictionary(wrappedDict, &isSuccess);
-    return isSuccess ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+    mEngine->SendSetDictionary(wrappedDict, &isSuccess);
+    if (!isSuccess) {
+      mCurrentDictionary.Truncate();
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    mCurrentDictionary = wrappedDict;
+    return NS_OK;
   }
 
   // Calls to mozISpellCheckingEngine::SetDictionary might destroy us
   nsRefPtr<mozSpellChecker> kungFuDeathGrip = this;
 
   mSpellCheckingEngine = nullptr;
 
   if (aDictionary.IsEmpty()) {
@@ -397,17 +418,17 @@ mozSpellChecker::SetCurrentDictionary(co
       nsXPIDLString language;
       nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv));
       NS_ENSURE_SUCCESS(rv, rv);
       return serv->GetUtil(language.get(),getter_AddRefs(mConverter));
     }
   }
 
   mSpellCheckingEngine = nullptr;
-  
+
   // We could not find any engine with the requested dictionary
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP 
 mozSpellChecker::CheckCurrentDictionary()
 {
   // If the current dictionary has been uninstalled, we need to stop using it.
@@ -510,16 +531,18 @@ mozSpellChecker::GetCurrentBlockIndex(ns
   *outBlockIndex = blockIndex;
 
   return result;
 }
 
 nsresult
 mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines)
 {
+  MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Content);
+
   nsresult rv;
   bool hasMoreEngines;
 
   nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
   if (!catMgr)
     return NS_ERROR_NULL_POINTER;
 
   nsCOMPtr<nsISimpleEnumerator> catEntries;
@@ -559,12 +582,8 @@ mozSpellChecker::GetEngineList(nsCOMArra
     // Fail if not succeeded to load HunSpell. Ignore errors
     // for external spellcheck engines.
     return rv;
   }
   aSpellCheckingEngines->AppendObject(engine);
 
   return NS_OK;
 }
-
-void mozSpellChecker::DeleteRemoteEngine() {
-  mEngine = nullptr;
-}
--- a/extensions/spellcheck/src/mozSpellChecker.h
+++ b/extensions/spellcheck/src/mozSpellChecker.h
@@ -44,28 +44,33 @@ public:
   NS_IMETHOD AddWordToPersonalDictionary(const nsAString &aWord);
   NS_IMETHOD RemoveWordFromPersonalDictionary(const nsAString &aWord);
   NS_IMETHOD GetPersonalDictionary(nsTArray<nsString> *aWordList);
 
   NS_IMETHOD GetDictionaryList(nsTArray<nsString> *aDictionaryList);
   NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary);
   NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary);
   NS_IMETHOD CheckCurrentDictionary();
-  void DeleteRemoteEngine();
+
+  void DeleteRemoteEngine() {
+    mEngine = nullptr;
+  }
 
 protected:
   virtual ~mozSpellChecker();
 
   nsCOMPtr<mozISpellI18NUtil> mConverter;
   nsCOMPtr<nsITextServicesDocument> mTsDoc;
   nsCOMPtr<mozIPersonalDictionary> mPersonalDictionary;
 
   nsCOMPtr<mozISpellCheckingEngine>  mSpellCheckingEngine;
   bool mFromStart;
 
+  nsString mCurrentDictionary;
+
   nsresult SetupDoc(int32_t *outBlockOffset);
 
   nsresult GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex);
 
   nsresult GetEngineList(nsCOMArray<mozISpellCheckingEngine> *aDictionaryList);
 
   mozilla::PRemoteSpellcheckEngineChild *mEngine;
 };
--- a/toolkit/content/widgets/textbox.xml
+++ b/toolkit/content/widgets/textbox.xml
@@ -533,17 +533,17 @@
         <xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
         <xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
         <xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
         <xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
         <xul:menuseparator/>
         <xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
         <xul:menuseparator anonid="spell-check-separator"/>
         <xul:menuitem label="&spellCheckToggle.label;" type="checkbox" accesskey="&spellCheckToggle.accesskey;" anonid="spell-check-enabled"
-                      oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled(window);"/>
+                      oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled();"/>
         <xul:menu label="&spellDictionaries.label;" accesskey="&spellDictionaries.accesskey;" anonid="spell-dictionaries">
           <xul:menupopup anonid="spell-dictionaries-menu"
                          onpopupshowing="event.stopPropagation();"
                          onpopuphiding="event.stopPropagation();"/>
         </xul:menu>
       </xul:menupopup>
     </content>
 
--- a/toolkit/modules/InlineSpellChecker.jsm
+++ b/toolkit/modules/InlineSpellChecker.jsm
@@ -1,17 +1,22 @@
 /* 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/. */
 
-this.EXPORTED_SYMBOLS = [ "InlineSpellChecker" ];
+this.EXPORTED_SYMBOLS = [ "InlineSpellChecker",
+                          "SpellCheckHelper" ];
 var gLanguageBundle;
 var gRegionBundle;
 const MAX_UNDO_STACK_DEPTH = 1;
 
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
 this.InlineSpellChecker = function InlineSpellChecker(aEditor) {
   this.init(aEditor);
   this.mAddedWordStack = []; // We init this here to preserve it between init/uninit calls
 }
 
 InlineSpellChecker.prototype = {
   // Call this function to initialize for a given editor
   init: function(aEditor)
@@ -21,19 +26,37 @@ InlineSpellChecker.prototype = {
     try {
       this.mInlineSpellChecker = this.mEditor.getInlineSpellChecker(true);
       // note: this might have been NULL if there is no chance we can spellcheck
     } catch(e) {
       this.mInlineSpellChecker = null;
     }
   },
 
+  initFromRemote: function(aSpellInfo)
+  {
+    if (this.mRemote)
+      throw new Error("Unexpected state");
+    this.uninit();
+
+    if (!aSpellInfo)
+      return;
+    this.mInlineSpellChecker = this.mRemote = new RemoteSpellChecker(aSpellInfo);
+    this.mOverMisspelling = aSpellInfo.overMisspelling;
+    this.mMisspelling = aSpellInfo.misspelling;
+  },
+
   // call this to clear state
   uninit: function()
   {
+    if (this.mRemote) {
+      this.mRemote.uninit();
+      this.mRemote = null;
+    }
+
     this.mEditor = null;
     this.mInlineSpellChecker = null;
     this.mOverMisspelling = false;
     this.mMisspelling = "";
     this.mMenu = null;
     this.mSpellSuggestions = [];
     this.mSuggestionItems = [];
     this.mDictionaryMenu = null;
@@ -68,53 +91,62 @@ InlineSpellChecker.prototype = {
   },
 
   // returns false if there should be no spellchecking UI enabled at all, true
   // means that you can at least give the user the ability to turn it on.
   get canSpellCheck()
   {
     // inline spell checker objects will be created only if there are actual
     // dictionaries available
-    return (this.mInlineSpellChecker != null);
+    if (this.mRemote)
+      return this.mRemote.canSpellCheck;
+    return this.mInlineSpellChecker != null;
   },
 
   get initialSpellCheckPending() {
+    if (this.mRemote) {
+      return this.mRemote.spellCheckPending;
+    }
     return !!(this.mInlineSpellChecker &&
               !this.mInlineSpellChecker.spellChecker &&
               this.mInlineSpellChecker.spellCheckPending);
   },
 
   // Whether spellchecking is enabled in the current box
   get enabled()
   {
+    if (this.mRemote)
+      return this.mRemote.enableRealTimeSpell;
     return (this.mInlineSpellChecker &&
             this.mInlineSpellChecker.enableRealTimeSpell);
   },
   set enabled(isEnabled)
   {
-    if (this.mInlineSpellChecker)
+    if (this.mRemote)
+      this.mRemote.setSpellcheckUserOverride(isEnabled);
+    else if (this.mInlineSpellChecker)
       this.mEditor.setSpellcheckUserOverride(isEnabled);
   },
 
   // returns true if the given event is over a misspelled word
   get overMisspelling()
   {
     return this.mOverMisspelling;
   },
 
   // this prepends up to "maxNumber" suggestions at the given menu position
   // for the word under the cursor. Returns the number of suggestions inserted.
   addSuggestionsToMenu: function(menu, insertBefore, maxNumber)
   {
-    if (! this.mInlineSpellChecker || ! this.mOverMisspelling)
+    if (!this.mRemote && (!this.mInlineSpellChecker || !this.mOverMisspelling))
       return 0; // nothing to do
 
-    var spellchecker = this.mInlineSpellChecker.spellChecker;
+    var spellchecker = this.mRemote || this.mInlineSpellChecker.spellChecker;
     try {
-      if (! spellchecker.CheckCurrentWord(this.mMisspelling))
+      if (!this.mRemote && !spellchecker.CheckCurrentWord(this.mMisspelling))
         return 0;  // word seems not misspelled after all (?)
     } catch(e) {
         return 0;
     }
 
     this.mMenu = menu;
     this.mSpellSuggestions = [];
     this.mSuggestionItems = [];
@@ -143,67 +175,76 @@ InlineSpellChecker.prototype = {
   clearSuggestionsFromMenu: function()
   {
     for (var i = 0; i < this.mSuggestionItems.length; i ++) {
       this.mMenu.removeChild(this.mSuggestionItems[i]);
     }
     this.mSuggestionItems = [];
   },
 
-  // returns the number of dictionary languages. If insertBefore is NULL, this
-  // does an append to the given menu
-  addDictionaryListToMenu: function(menu, insertBefore)
-  {
-    this.mDictionaryMenu = menu;
-    this.mDictionaryNames = [];
-    this.mDictionaryItems = [];
-
-    if (! this.mInlineSpellChecker || ! this.enabled)
-      return 0;
-    var spellchecker = this.mInlineSpellChecker.spellChecker;
-
-    // Cannot access the dictionary list from another process so just return 0.
-    if (Components.utils.isCrossProcessWrapper(spellchecker))
-      return 0;
-
-    var o1 = {}, o2 = {};
-    spellchecker.GetDictionaryList(o1, o2);
-    var list = o1.value;
-    var listcount = o2.value;
-    var curlang = "";
-    try {
-        curlang = spellchecker.GetCurrentDictionary();
-    } catch(e) {}
-
+  sortDictionaryList: function(list) {
     var sortedList = [];
     for (var i = 0; i < list.length; i ++) {
       sortedList.push({"id": list[i],
                        "label": this.getDictionaryDisplayName(list[i])});
     }
     sortedList.sort(function(a, b) {
       if (a.label < b.label)
         return -1;
       if (a.label > b.label)
         return 1;
       return 0;
     });
 
+    return sortedList;
+  },
+
+  // returns the number of dictionary languages. If insertBefore is NULL, this
+  // does an append to the given menu
+  addDictionaryListToMenu: function(menu, insertBefore)
+  {
+    this.mDictionaryMenu = menu;
+    this.mDictionaryNames = [];
+    this.mDictionaryItems = [];
+
+    if (!this.enabled)
+      return 0;
+
+    var list;
+    var curlang = "";
+    if (this.mRemote) {
+      list = this.mRemote.dictionaryList;
+      curlang = this.mRemote.currentDictionary;
+    }
+    else if (this.mInlineSpellChecker) {
+      var spellchecker = this.mInlineSpellChecker.spellChecker;
+      var o1 = {}, o2 = {};
+      spellchecker.GetDictionaryList(o1, o2);
+      list = o1.value;
+      var listcount = o2.value;
+      try {
+        curlang = spellchecker.GetCurrentDictionary();
+      } catch(e) {}
+    }
+
+    var sortedList = this.sortDictionaryList(list);
+
     for (var i = 0; i < sortedList.length; i ++) {
       this.mDictionaryNames.push(sortedList[i].id);
       var item = menu.ownerDocument.createElement("menuitem");
       item.setAttribute("id", "spell-check-dictionary-" + sortedList[i].id);
       item.setAttribute("label", sortedList[i].label);
       item.setAttribute("type", "radio");
       this.mDictionaryItems.push(item);
       if (curlang == sortedList[i].id) {
         item.setAttribute("checked", "true");
       } else {
         var callback = function(me, val) {
           return function(evt) {
-            me.selectDictionary(val, menu.ownerDocument.defaultView);
+            me.selectDictionary(val);
           }
         };
         item.addEventListener("command", callback(this, i), true);
       }
       if (insertBefore)
         menu.insertBefore(item, insertBefore);
       else
         menu.appendChild(item);
@@ -276,87 +317,257 @@ InlineSpellChecker.prototype = {
   {
     for (var i = 0; i < this.mDictionaryItems.length; i ++) {
       this.mDictionaryMenu.removeChild(this.mDictionaryItems[i]);
     }
     this.mDictionaryItems = [];
   },
 
   // callback for selecting a dictionary
-  selectDictionary: function(index, aWindow)
+  selectDictionary: function(index)
   {
-    // Avoid a crash in multiprocess until Bug 1030451 lands
-    // Remove aWindow parameter at that time
-    const Ci = Components.interfaces;
-    let chromeFlags = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
-                                  getInterface(Ci.nsIWebNavigation).
-                                  QueryInterface(Ci.nsIDocShellTreeItem).treeOwner.
-                                  QueryInterface(Ci.nsIInterfaceRequestor).
-                                  getInterface(Ci.nsIXULWindow).chromeFlags;
-    let chromeRemoteWindow = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW;
-    if (chromeFlags & chromeRemoteWindow) {
+    if (this.mRemote) {
+      this.mRemote.selectDictionary(index);
       return;
     }
     if (! this.mInlineSpellChecker || index < 0 || index >= this.mDictionaryNames.length)
       return;
     var spellchecker = this.mInlineSpellChecker.spellChecker;
     spellchecker.SetCurrentDictionary(this.mDictionaryNames[index]);
     this.mInlineSpellChecker.spellCheckRange(null); // causes recheck
   },
 
   // callback for selecting a suggesteed replacement
   replaceMisspelling: function(index)
   {
+    if (this.mRemote) {
+      this.mRemote.replaceMisspelling(index);
+      return;
+    }
     if (! this.mInlineSpellChecker || ! this.mOverMisspelling)
       return;
     if (index < 0 || index >= this.mSpellSuggestions.length)
       return;
     this.mInlineSpellChecker.replaceWord(this.mWordNode, this.mWordOffset,
                                          this.mSpellSuggestions[index]);
   },
 
   // callback for enabling or disabling spellchecking
-  toggleEnabled: function(aWindow)
+  toggleEnabled: function()
   {
-    // Avoid a crash in multiprocess until Bug 1030451 lands
-    // Remove aWindow parameter at that time
-    const Ci = Components.interfaces;
-    let chromeFlags = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
-                                  getInterface(Ci.nsIWebNavigation).
-                                  QueryInterface(Ci.nsIDocShellTreeItem).treeOwner.
-                                  QueryInterface(Ci.nsIInterfaceRequestor).
-                                  getInterface(Ci.nsIXULWindow).chromeFlags;
-    let chromeRemoteWindow = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW;
-    if (chromeFlags & chromeRemoteWindow) {
-      return;
-    }
-    this.mEditor.setSpellcheckUserOverride(!this.mInlineSpellChecker.enableRealTimeSpell);
+    if (this.mRemote)
+      this.mRemote.toggleEnabled();
+    else
+      this.mEditor.setSpellcheckUserOverride(!this.mInlineSpellChecker.enableRealTimeSpell);
   },
 
   // callback for adding the current misspelling to the user-defined dictionary
   addToDictionary: function()
   {
     // Prevent the undo stack from growing over the max depth
     if (this.mAddedWordStack.length == MAX_UNDO_STACK_DEPTH)
       this.mAddedWordStack.shift();
 
     this.mAddedWordStack.push(this.mMisspelling);
-    this.mInlineSpellChecker.addWordToDictionary(this.mMisspelling);
+    if (this.mRemote)
+      this.mRemote.addToDictionary();
+    else {
+      this.mInlineSpellChecker.addWordToDictionary(this.mMisspelling);
+    }
   },
   // callback for removing the last added word to the dictionary LIFO fashion
   undoAddToDictionary: function()
   {
     if (this.mAddedWordStack.length > 0)
     {
       var word = this.mAddedWordStack.pop();
-      this.mInlineSpellChecker.removeWordFromDictionary(word);
+      if (this.mRemote)
+        this.mRemote.undoAddToDictionary(word);
+      else
+        this.mInlineSpellChecker.removeWordFromDictionary(word);
     }
   },
   canUndo : function()
   {
     // Return true if we have words on the stack
     return (this.mAddedWordStack.length > 0);
   },
   ignoreWord: function()
   {
-    this.mInlineSpellChecker.ignoreWord(this.mMisspelling);
+    if (this.mRemote)
+      this.mRemote.ignoreWord();
+    else
+      this.mInlineSpellChecker.ignoreWord(this.mMisspelling);
   }
 };
+
+var SpellCheckHelper = {
+  // Set when over a non-read-only <textarea> or editable <input>.
+  EDITABLE: 0x1,
+
+  // Set when over an <input> element of any type.
+  INPUT: 0x2,
+
+  // Set when over any <textarea>.
+  TEXTAREA: 0x4,
+
+  // Set when over any text-entry <input>.
+  TEXTINPUT: 0x8,
+
+  // Set when over an <input> that can be used as a keyword field.
+  KEYWORD: 0x10,
+
+  // Set when over an element that otherwise would not be considered
+  // "editable" but is because content editable is enabled for the document.
+  CONTENTEDITABLE: 0x20,
+
+  isTargetAKeywordField(aNode, window) {
+    if (!(aNode instanceof window.HTMLInputElement))
+      return false;
+
+    var form = aNode.form;
+    if (!form || aNode.type == "password")
+      return false;
+
+    var method = form.method.toUpperCase();
+
+    // These are the following types of forms we can create keywords for:
+    //
+    // method   encoding type       can create keyword
+    // GET      *                                 YES
+    //          *                                 YES
+    // POST                                       YES
+    // POST     application/x-www-form-urlencoded YES
+    // POST     text/plain                        NO (a little tricky to do)
+    // POST     multipart/form-data               NO
+    // POST     everything else                   YES
+    return (method == "GET" || method == "") ||
+           (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
+  },
+
+  // Returns the computed style attribute for the given element.
+  getComputedStyle(aElem, aProp) {
+    return aElem.ownerDocument
+                .defaultView
+                .getComputedStyle(aElem, "").getPropertyValue(aProp);
+  },
+
+  isEditable(element, window) {
+    var flags = 0;
+    if (element instanceof window.HTMLInputElement) {
+      flags |= this.INPUT;
+      if (element.mozIsTextField(false)) {
+        flags |= this.TEXTINPUT;
+
+        // Allow spellchecking UI on all text and search inputs.
+        if (!element.readOnly &&
+            (element.type == "text" || element.type == "search")) {
+          flags |= this.EDITABLE;
+        }
+        if (this.isTargetAKeywordField(element, window))
+          flags |= this.KEYWORD;
+      }
+    } else if (element instanceof window.HTMLTextAreaElement) {
+      flags |= this.TEXTINPUT | this.TEXTAREA;
+      if (!element.readOnly) {
+        flags |= this.EDITABLE;
+      }
+    }
+
+    if (!(flags & this.EDITABLE)) {
+      var win = element.ownerDocument.defaultView;
+      if (win) {
+        var isEditable = false;
+        try {
+          var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIWebNavigation)
+                                  .QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIEditingSession);
+          if (editingSession.windowIsEditable(win) &&
+              this.getComputedStyle(element, "-moz-user-modify") == "read-write") {
+            isEditable = true;
+          }
+        }
+        catch(ex) {
+          // If someone built with composer disabled, we can't get an editing session.
+        }
+
+        if (isEditable)
+          flags |= this.CONTENTEDITABLE;
+      }
+    }
+
+    return flags;
+  },
+};
+
+function RemoteSpellChecker(aSpellInfo) {
+  this._spellInfo = aSpellInfo;
+  this._suggestionGenerator = null;
+}
+
+RemoteSpellChecker.prototype = {
+  get canSpellCheck() { return this._spellInfo.canSpellCheck; },
+  get spellCheckPending() { return this._spellInfo.initialSpellCheckPending; },
+  get overMisspelling() { return this._spellInfo.overMisspelling; },
+  get enableRealTimeSpell() { return this._spellInfo.enableRealTimeSpell; },
+
+  GetSuggestedWord() {
+    if (!this._suggestionGenerator) {
+      this._suggestionGenerator = (function*(spellInfo) {
+        for (let i of spellInfo.spellSuggestions)
+          yield i;
+      })(this._spellInfo);
+    }
+
+    let next = this._suggestionGenerator.next();
+    if (next.done) {
+      this._suggestionGenerator = null;
+      return "";
+    }
+    return next.value;
+  },
+
+  get currentDictionary() { return this._spellInfo.currentDictionary },
+  get dictionaryList() { return this._spellInfo.dictionaryList.slice(); },
+
+  selectDictionary(index) {
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:selectDictionary",
+                                            { index });
+  },
+
+  replaceMisspelling(index) {
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:replaceMisspelling",
+                                            { index });
+  },
+
+  toggleEnabled() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:toggleEnabled", {}); },
+  addToDictionary() {
+    // This is really ugly. There is an nsISpellChecker somewhere in the
+    // parent that corresponds to our current element's spell checker in the
+    // child, but it's hard to access it. However, we know that
+    // addToDictionary adds the word to the singleton personal dictionary, so
+    // we just do that here.
+    // NB: We also rely on the fact that we only ever pass an empty string in
+    // as the "lang".
+
+    let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
+                       .getService(Ci.mozIPersonalDictionary);
+    dictionary.addWord(this._spellInfo.misspelling, "");
+
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
+  },
+  undoAddToDictionary(word) {
+    let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
+                       .getService(Ci.mozIPersonalDictionary);
+    dictionary.removeWord(word, "");
+
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
+  },
+  ignoreWord() {
+    let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
+                       .getService(Ci.mozIPersonalDictionary);
+    dictionary.ignoreWord(this._spellInfo.misspelling);
+
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
+  },
+  uninit() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:uninit", {}); }
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/InlineSpellCheckerContent.jsm
@@ -0,0 +1,141 @@
+/* vim: set ts=2 sw=2 sts=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+let { SpellCheckHelper } = Cu.import("resource://gre/modules/InlineSpellChecker.jsm");
+
+this.EXPORTED_SYMBOLS = [ "InlineSpellCheckerContent" ]
+
+var InlineSpellCheckerContent = {
+  _spellChecker: null,
+  _manager: null,
+
+  initContextMenu(event, editFlags, messageManager) {
+    this._manager = messageManager;
+
+    let spellChecker;
+    if (!(editFlags & (SpellCheckHelper.TEXTAREA | SpellCheckHelper.INPUT))) {
+      // Get the editor off the window.
+      let win = event.target.ownerDocument.defaultView;
+      let editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebNavigation)
+                              .QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIEditingSession);
+      spellChecker = this._spellChecker =
+        new InlineSpellChecker(editingSession.getEditorForWindow(win));
+    } else {
+      // Use the element's editor.
+      spellChecker = this._spellChecker =
+        new InlineSpellChecker(event.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+    }
+
+    this._spellChecker.initFromEvent(event.rangeParent, event.rangeOffset)
+
+    this._addMessageListeners();
+
+    if (!spellChecker.canSpellCheck) {
+      return { canSpellCheck: false,
+               initialSpellCheckPending: true,
+               enableRealTimeSpell: false };
+    }
+
+    if (!spellChecker.mInlineSpellChecker.enableRealTimeSpell) {
+      return { canSpellCheck: true,
+               initialSpellCheckPending: spellChecker.initialSpellCheckPending,
+               enableRealTimeSpell: false };
+    }
+
+    let dictionaryList = {};
+    let realSpellChecker = spellChecker.mInlineSpellChecker.spellChecker;
+    realSpellChecker.GetDictionaryList(dictionaryList, {});
+
+    // The original list we get is in random order. We need our list to be
+    // sorted by display names.
+    dictionaryList = spellChecker.sortDictionaryList(dictionaryList.value).map((obj) => {
+      return obj.id;
+    });
+    spellChecker.mDictionaryNames = dictionaryList;
+
+    return { canSpellCheck: spellChecker.canSpellCheck,
+             initialSpellCheckPending: spellChecker.initialSpellCheckPending,
+             enableRealTimeSpell: spellChecker.enabled,
+             overMisspelling: spellChecker.overMisspelling,
+             misspelling: spellChecker.mMisspelling,
+             spellSuggestions: this._generateSpellSuggestions(),
+             currentDictionary: spellChecker.mInlineSpellChecker.spellChecker.GetCurrentDictionary(),
+             dictionaryList: dictionaryList };
+  },
+
+  uninitContextMenu() {
+    for (let i of this._messages)
+      this._manager.removeMessageListener(i, this);
+
+    this._manager = null;
+    this._spellChecker = null;
+  },
+
+  _generateSpellSuggestions() {
+    let spellChecker = this._spellChecker.mInlineSpellChecker.spellChecker;
+    try {
+      spellChecker.CheckCurrentWord(this._spellChecker.mMisspelling);
+    } catch (e) {
+      return [];
+    }
+
+    let suggestions = new Array(5);
+    for (let i = 0; i < 5; ++i) {
+      suggestions[i] = spellChecker.GetSuggestedWord();
+      if (suggestions[i].length === 0) {
+        suggestions.length = i;
+        break;
+      }
+    }
+
+    this._spellChecker.mSpellSuggestions = suggestions;
+    return suggestions;
+  },
+
+  _messages: [
+      "InlineSpellChecker:selectDictionary",
+      "InlineSpellChecker:replaceMisspelling",
+      "InlineSpellChecker:toggleEnabled",
+
+      "InlineSpellChecker:recheck",
+
+      "InlineSpellChecker:uninit"
+    ],
+
+  _addMessageListeners() {
+    for (let i of this._messages)
+      this._manager.addMessageListener(i, this);
+  },
+
+  receiveMessage(msg) {
+    switch (msg.name) {
+      case "InlineSpellChecker:selectDictionary":
+        this._spellChecker.selectDictionary(msg.data.index);
+        break;
+
+      case "InlineSpellChecker:replaceMisspelling":
+        this._spellChecker.replaceMisspelling(msg.data.index);
+        break;
+
+      case "InlineSpellChecker:toggleEnabled":
+        this._spellChecker.toggleEnabled();
+        break;
+
+      case "InlineSpellChecker:recheck":
+        this._spellChecker.mInlineSpellChecker.enableRealTimeSpell = true;
+        break;
+
+      case "InlineSpellChecker:uninit":
+        this.uninitContextMenu();
+        break;
+    }
+  }
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -20,16 +20,17 @@ EXTRA_JS_MODULES += [
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'Dict.jsm',
     'FileUtils.jsm',
     'Finder.jsm',
     'Geometry.jsm',
     'Http.jsm',
     'InlineSpellChecker.jsm',
+    'InlineSpellCheckerContent.jsm',
     'LoadContextInfo.jsm',
     'Log.jsm',
     'NewTabUtils.jsm',
     'PageMenu.jsm',
     'PermissionsUtils.jsm',
     'PopupNotifications.jsm',
     'Preferences.jsm',
     'PrivateBrowsingUtils.jsm',