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).
This commit is contained in:
Blake Kaplan 2014-10-03 10:52:37 -04:00
parent 1401d14594
commit 8917309372
23 changed files with 645 additions and 237 deletions

View File

@ -8,6 +8,8 @@ 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");
@ -100,7 +102,15 @@ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
}
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 {

View File

@ -1,8 +1,10 @@
/* 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;
@ -518,11 +520,13 @@ nsContextMenu.prototype = {
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;
}
@ -578,6 +582,7 @@ nsContextMenu.prototype = {
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)
@ -628,24 +633,19 @@ nsContextMenu.prototype = {
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);
}
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);
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 = (editFlags & SpellCheckHelper.KEYWORD);
}
else if (this.target instanceof HTMLHtmlElement) {
var bodyElt = this.target.ownerDocument.body;
@ -748,41 +748,35 @@ nsContextMenu.prototype = {
// 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;
}
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);
}
catch(ex) {
// If someone built with composer disabled, we can't get an editing session.
}
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);
}
}
},
@ -1502,30 +1496,6 @@ nsContextMenu.prototype = {
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.

View File

@ -3049,8 +3049,13 @@
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);

View File

@ -53,6 +53,7 @@
#include "mozilla/unused.h"
#include "mozInlineSpellChecker.h"
#include "nsIConsoleListener.h"
#include "nsICycleCollectorListener.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
@ -77,6 +78,7 @@
#include "nsIJSRuntimeService.h"
#include "nsThreadManager.h"
#include "nsAnonymousTemporaryFile.h"
#include "nsISpellChecker.h"
#include "IHistory.h"
#include "nsNetUtil.h"
@ -693,7 +695,7 @@ ContentChild::InitXPCOM()
NS_WARNING("Couldn't register console listener for child process");
bool isOffline;
SendGetXPCOMProcessAttributes(&isOffline);
SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries);
RecvSetOffline(isOffline);
DebugOnly<FileUpdateDispatcher*> observer = FileUpdateDispatcher::GetSingleton();
@ -1125,6 +1127,12 @@ ContentChild::RecvPBrowserConstructor(PBrowserChild* aActor,
return true;
}
void
ContentChild::GetAvailableDictionaries(InfallibleTArray<nsString>& aDictionaries)
{
aDictionaries = mAvailableDictionaries;
}
PFileDescriptorSetChild*
ContentChild::AllocPFileDescriptorSetChild(const FileDescriptor& aFD)
{
@ -1153,7 +1161,7 @@ ContentChild::AllocPBlobChild(const BlobConstructorParams& 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;
}
@ -1742,6 +1750,14 @@ ContentChild::RecvGeolocationUpdate(const GeoPosition& somewhere)
return true;
}
bool
ContentChild::RecvUpdateDictionaryList(const InfallibleTArray<nsString>& aDictionaries)
{
mAvailableDictionaries = aDictionaries;
mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking();
return true;
}
bool
ContentChild::RecvAddPermission(const IPC::Permission& permission)
{

View File

@ -293,6 +293,8 @@ public:
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;
@ -381,6 +383,8 @@ public:
const bool& aIsForApp,
const bool& aIsForBrowser) MOZ_OVERRIDE;
void GetAvailableDictionaries(InfallibleTArray<nsString>& aDictionaries);
private:
virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
@ -397,6 +401,8 @@ private:
nsTHashtable<nsPtrHashKey<nsIObserver>> mIdleObservers;
InfallibleTArray<nsString> mAvailableDictionaries;
/**
* An ID unique to the process containing our corresponding
* content parent.

View File

@ -101,6 +101,7 @@
#include "nsIPresShell.h"
#include "nsIScriptError.h"
#include "nsISiteSecurityService.h"
#include "nsISpellChecker.h"
#include "nsIStyleSheet.h"
#include "nsISupportsPrimitives.h"
#include "nsIURIFixup.h"
@ -2496,7 +2497,8 @@ ContentParent::RecvAddNewProcess(const uint32_t& aPid,
// Update offline settings.
bool isOffline;
RecvGetXPCOMProcessAttributes(&isOffline);
InfallibleTArray<nsString> unusedDictionaries;
RecvGetXPCOMProcessAttributes(&isOffline, &unusedDictionaries);
content->SendSetOffline(isOffline);
PreallocatedProcessManager::PublishSpareProcess(content);
@ -2744,12 +2746,18 @@ ContentParent::RecvGetProcessAttributes(uint64_t* aId,
}
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;
}
@ -4053,6 +4061,23 @@ ContentParent::IgnoreIPCPrincipal()
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

View File

@ -139,6 +139,8 @@ public:
static bool IgnoreIPCPrincipal();
static void NotifyUpdatedDictionaries();
virtual bool RecvCreateChildProcess(const IPCTabContext& aContext,
const hal::ProcessPriority& aPriority,
uint64_t* aId,
@ -410,7 +412,9 @@ private:
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;

View File

@ -433,6 +433,8 @@ child:
GeolocationUpdate(GeoPosition somewhere);
UpdateDictionaryList(nsString[] dictionaries);
// nsIPermissionManager messages
AddPermission(Permission permission);
@ -442,7 +444,7 @@ child:
GarbageCollect();
CycleCollect();
/**
* Start accessibility engine in content process.
*/
@ -504,7 +506,7 @@ parent:
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)

View File

@ -109,7 +109,9 @@ LOCAL_INCLUDES += [
'/dom/geolocation',
'/dom/mobilemessage/ipc',
'/dom/storage',
'/editor/libeditor',
'/extensions/cookie',
'/extensions/spellcheck/src',
'/hal/sandbox',
'/js/ipc',
'/layout/base',

View File

@ -1,4 +1,5 @@
/* -*- 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/. */
@ -46,16 +47,16 @@
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();
}
@ -699,23 +700,15 @@ nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallba
}
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;
}
@ -723,14 +716,13 @@ nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallba
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
@ -742,10 +734,9 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
// 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);
}
@ -773,8 +764,8 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
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!

View File

@ -6,17 +6,17 @@ 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

View File

@ -5,8 +5,9 @@
#include "RemoteSpellCheckEngineChild.h"
namespace mozilla {
RemoteSpellcheckEngineChild::RemoteSpellcheckEngineChild(mozSpellChecker *aOwner)
:mOwner(aOwner)
: mOwner(aOwner)
{
}
@ -15,7 +16,6 @@ RemoteSpellcheckEngineChild::~RemoteSpellcheckEngineChild()
// null out the owner's SpellcheckEngineChild to prevent state corruption
// during shutdown
mOwner->DeleteRemoteEngine();
}
} //namespace mozilla

View File

@ -11,11 +11,12 @@
class mozSpellChecker;
namespace mozilla {
class RemoteSpellcheckEngineChild : public mozilla::PRemoteSpellcheckEngineChild
{
public:
explicit RemoteSpellcheckEngineChild(mozSpellChecker *aOwner);
~RemoteSpellcheckEngineChild();
virtual ~RemoteSpellcheckEngineChild();
private:
mozSpellChecker *mOwner;

View File

@ -1,18 +1,17 @@
/* 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()
@ -20,43 +19,38 @@ 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;
}
@ -67,4 +61,3 @@ RemoteSpellcheckEngineParent::ActorDestroy(ActorDestroyReason aWhy)
}
} // namespace mozilla

View File

@ -4,34 +4,34 @@
#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);
bool AnswerCheck( const nsString& aWord, bool* aIsMisspelled);
bool AnswerCheckAndSuggest(
const nsString& aWord,
bool* aIsMisspelled,
InfallibleTArray<nsString>* aSuggestions);
virtual bool RecvSetDictionary(const nsString& aDictionary,
bool* success) MOZ_OVERRIDE;
virtual bool RecvCheck(const nsString& aWord, bool* aIsMisspelled) MOZ_OVERRIDE;
virtual bool RecvCheckAndSuggest(const nsString& aWord,
bool* aIsMisspelled,
InfallibleTArray<nsString>* aSuggestions)
MOZ_OVERRIDE;
private:
nsCOMPtr<mozISpellCheckingEngine> mEngine;
nsCOMPtr<nsISpellChecker> mSpellChecker;
};
}

View File

@ -76,7 +76,9 @@
#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)
@ -113,7 +115,7 @@ mozHunspell::mozHunspell()
nsresult
mozHunspell::Init()
{
LoadDictionaryList();
LoadDictionaryList(false);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
@ -344,7 +346,7 @@ NS_IMETHODIMP mozHunspell::GetDictionaryList(char16_t ***aDictionaries,
}
void
mozHunspell::LoadDictionaryList()
mozHunspell::LoadDictionaryList(bool aNotifyChildProcesses)
{
mDictionaries.Clear();
@ -424,6 +426,10 @@ mozHunspell::LoadDictionaryList()
// 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()) {
@ -589,7 +595,7 @@ mozHunspell::Observe(nsISupports* aSubj, const char *aTopic,
|| !strcmp(aTopic, "profile-after-change"),
"Unexpected observer topic");
LoadDictionaryList();
LoadDictionaryList(false);
return NS_OK;
}
@ -598,7 +604,7 @@ mozHunspell::Observe(nsISupports* aSubj, const char *aTopic,
NS_IMETHODIMP mozHunspell::AddDirectory(nsIFile *aDir)
{
mDynamicDirectories.AppendObject(aDir);
LoadDictionaryList();
LoadDictionaryList(true);
return NS_OK;
}
@ -606,6 +612,6 @@ NS_IMETHODIMP mozHunspell::AddDirectory(nsIFile *aDir)
NS_IMETHODIMP mozHunspell::RemoveDirectory(nsIFile *aDir)
{
mDynamicDirectories.RemoveObject(aDir);
LoadDictionaryList();
LoadDictionaryList(true);
return NS_OK;
}

View File

@ -96,7 +96,7 @@ public:
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);

View File

@ -1,3 +1,4 @@
/* 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/. */
@ -34,38 +35,41 @@ NS_IMPL_CYCLE_COLLECTION(mozSpellChecker,
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)
@ -130,9 +134,9 @@ mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray
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;
}
@ -302,9 +306,15 @@ mozSpellChecker::GetPersonalDictionary(nsTArray<nsString> *aWordList)
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
@ -344,9 +354,14 @@ mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList)
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;
@ -358,14 +373,20 @@ mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
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
@ -402,7 +423,7 @@ mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
}
mSpellCheckingEngine = nullptr;
// We could not find any engine with the requested dictionary
return NS_ERROR_NOT_AVAILABLE;
}
@ -515,6 +536,8 @@ mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *ou
nsresult
mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines)
{
MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Content);
nsresult rv;
bool hasMoreEngines;
@ -564,7 +587,3 @@ mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellChecki
return NS_OK;
}
void mozSpellChecker::DeleteRemoteEngine() {
mEngine = nullptr;
}

View File

@ -49,7 +49,10 @@ public:
NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary);
NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary);
NS_IMETHOD CheckCurrentDictionary();
void DeleteRemoteEngine();
void DeleteRemoteEngine() {
mEngine = nullptr;
}
protected:
virtual ~mozSpellChecker();
@ -61,6 +64,8 @@ protected:
nsCOMPtr<mozISpellCheckingEngine> mSpellCheckingEngine;
bool mFromStart;
nsString mCurrentDictionary;
nsresult SetupDoc(int32_t *outBlockOffset);
nsresult GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex);

View File

@ -538,7 +538,7 @@
<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();"

View File

@ -2,11 +2,16 @@
* 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
@ -26,9 +31,27 @@ InlineSpellChecker.prototype = {
}
},
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;
@ -73,10 +96,15 @@ InlineSpellChecker.prototype = {
{
// 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);
@ -85,12 +113,16 @@ InlineSpellChecker.prototype = {
// 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);
},
@ -104,12 +136,12 @@ InlineSpellChecker.prototype = {
// 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;
@ -148,31 +180,7 @@ InlineSpellChecker.prototype = {
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],
@ -186,6 +194,39 @@ InlineSpellChecker.prototype = {
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");
@ -198,7 +239,7 @@ InlineSpellChecker.prototype = {
} 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);
@ -281,18 +322,10 @@ InlineSpellChecker.prototype = {
},
// 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)
@ -305,6 +338,10 @@ InlineSpellChecker.prototype = {
// 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)
@ -314,21 +351,12 @@ InlineSpellChecker.prototype = {
},
// 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
@ -339,7 +367,11 @@ InlineSpellChecker.prototype = {
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()
@ -347,7 +379,10 @@ InlineSpellChecker.prototype = {
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()
@ -357,6 +392,182 @@ InlineSpellChecker.prototype = {
},
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", {}); }
};

View File

@ -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;
}
}
};

View File

@ -25,6 +25,7 @@ EXTRA_JS_MODULES += [
'Geometry.jsm',
'Http.jsm',
'InlineSpellChecker.jsm',
'InlineSpellCheckerContent.jsm',
'LoadContextInfo.jsm',
'Log.jsm',
'NewTabUtils.jsm',