--- 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="©Cmd.label;" accesskey="©Cmd.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',