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 231926 91ffa2ab03aa45d35668db72612e32049d8b241b
parent 231925 1b532d05bccf46194922ec52acf91e78bcdd0a04
child 231927 2f69737e5b1c013845ec6e7446cc2c9c25458717
child 231971 43f525528c4291b5d761544d985bb3fff39d2309
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, billm, felipe
bugs1026099
milestone35.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 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',