mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 22:53:28 +00:00
e8f234939e
- Bug 967494 - "Preference Composition/Spelling/Language is ignored, and changing spellcheck language in one composition window affects all open and new compositions" [r=ehsan] Bug 1157421 - Fix typo "suggesteed". r=ehsan (d48b61df9) - namespaces (c9e3edbf1) - Bug 1167409 - 3/4 - Change ScriptLoadRequeest::mLoading to mProgress. r=jandem (34377ba15) - Bug 1104732 - having deferred scripts shouldn't cause async scripts to delay domcontentloaded, r?smaug (f6b3a5ad9) - Bug 1138395 - Optimize nsDocument::mExpandoAndGeneration.expando out from the cc graphs when possible, r=mccr8 (d944130ab) - Bug 874838 - Make CreateElem return Element. r=khuey (ac65a35cf) - Bug 1194619 - fix comment r=dholbert (017a488a2) - Bug 1137494 - Change the type given to type validation check. r=jgilbert (05885cc7c) - Bug 1106138 - Remove the early unpremultiply in WebGLContext::SurfaceFromElementResultToImageSurface, and let the texel conversion code handle it instead. r=jgilbert (b8010b16b) - Bug 1185815 - Hoist generation increment. r=jgilbert (f6a276b5e) - Bug 1175931 - TexImageFromVideoElement uses GL_HALF_FLOAT if it does not support GL_HALF_FLOAT_OES which would be the case on non-ANGLE systems. Using GL_HALF_FLOAT_OES on a non OES system would result in an error when using TexImage2D. r=jgilbert (d692281f1) - Bug 1184534 - Add support for target size retrieval to GLX backend. r=jgilbert (0e5ba1f8e) - bug 1174705 - add GLContext::GetDefaultFramebuffer. r=jgilbert (99c0e70aa) - Bug 1179935, introduce complex viewport projections to Compositor, remove PrepareViewport; r=mstange (1753d65d3) - Bug 1184534 - Fetch viewport size from context in CompositorOGL, discard if changed during composition. r=nical (4f57bc4ed) - Bug 1187440 - Implement GLX shared surfaces on the OpenGL compositor. r=jgilbert,nical (4844e96ce) - Bug 1033375 - Nudge simple linear gradients with hard stops to half-pixel gradient. r=nical (331ddd4fa) - Bug 1185636 - Remove hard stop workaround for Cairo due to regressions. r=jrmuizel (ccefe7abc) - Bug 1177807 - Mark cairo surface dirty in ReleaseBits. r=jrmuizel (ae9d508b9) - Bug 1170390 - Detect 16bpp cairo xlib surface format. r=jrmuizel (25857ae30) - Bug 1019063 - Check for ::CreateDCW failing when printing. r=dvander (7f54ba8d2) - Bug 1170390 - Add gfxASurface::GetSurfaceFormat for retrieving precise surface format where necessary. r=jrmuizel (f70d11b29) - Bug 1155626 - Don't assume that Factory::GetD2D1Device returns a non-null device and add some gfxCriticalLog. r=Bas (0c896a368) - Bug 1182209 - Additional info with some critical errors. r=mchang CLOSED TREE (f4841baec)
929 lines
29 KiB
C++
929 lines
29 KiB
C++
/* -*- 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 final
|
|
#include "mozilla/Preferences.h" // for Preferences
|
|
#include "mozilla/Services.h" // for GetXULChromeRegistryService
|
|
#include "mozilla/dom/Element.h" // for Element
|
|
#include "mozilla/dom/Selection.h"
|
|
#include "mozilla/mozalloc.h" // for operator delete, etc
|
|
#include "nsAString.h" // for nsAString_internal::IsEmpty, etc
|
|
#include "nsComponentManagerUtils.h" // for do_CreateInstance
|
|
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
|
|
#include "nsDependentSubstring.h" // for Substring
|
|
#include "nsEditorSpellCheck.h"
|
|
#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc
|
|
#include "nsIChromeRegistry.h" // for nsIXULChromeRegistry
|
|
#include "nsIContent.h" // for nsIContent
|
|
#include "nsIContentPrefService.h" // for nsIContentPrefService, etc
|
|
#include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc
|
|
#include "nsIDOMDocument.h" // for nsIDOMDocument
|
|
#include "nsIDOMElement.h" // for nsIDOMElement
|
|
#include "nsIDocument.h" // for nsIDocument
|
|
#include "nsIEditor.h" // for nsIEditor
|
|
#include "nsIHTMLEditor.h" // for nsIHTMLEditor
|
|
#include "nsILoadContext.h"
|
|
#include "nsISelection.h" // for nsISelection
|
|
#include "nsISpellChecker.h" // for nsISpellChecker, etc
|
|
#include "nsISupportsBase.h" // for nsISupports
|
|
#include "nsISupportsUtils.h" // for NS_ADDREF
|
|
#include "nsITextServicesDocument.h" // for nsITextServicesDocument
|
|
#include "nsITextServicesFilter.h" // for nsITextServicesFilter
|
|
#include "nsIURI.h" // for nsIURI
|
|
#include "nsIVariant.h" // for nsIWritableVariant, etc
|
|
#include "nsLiteralString.h" // for NS_LITERAL_STRING, etc
|
|
#include "nsMemory.h" // for nsMemory
|
|
#include "nsRange.h"
|
|
#include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc
|
|
#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
|
|
#include "nsIPlaintextEditor.h" // for editor flags
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
class UpdateDictionaryHolder {
|
|
private:
|
|
nsEditorSpellCheck* mSpellCheck;
|
|
public:
|
|
explicit UpdateDictionaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
|
|
if (mSpellCheck) {
|
|
mSpellCheck->BeginUpdateDictionary();
|
|
}
|
|
}
|
|
~UpdateDictionaryHolder() {
|
|
if (mSpellCheck) {
|
|
mSpellCheck->EndUpdateDictionary();
|
|
}
|
|
}
|
|
};
|
|
|
|
#define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
|
|
|
|
/**
|
|
* Gets the URI of aEditor's document.
|
|
*/
|
|
static nsresult
|
|
GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aEditor);
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
|
|
nsCOMPtr<nsIDOMDocument> domDoc;
|
|
aEditor->GetDocument(getter_AddRefs(domDoc));
|
|
NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
|
|
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
|
|
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
|
|
|
|
nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
|
|
NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
|
|
|
|
*aURI = docUri;
|
|
NS_ADDREF(*aURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
static already_AddRefed<nsILoadContext>
|
|
GetLoadContext(nsIEditor* aEditor)
|
|
{
|
|
nsCOMPtr<nsIDOMDocument> domDoc;
|
|
aEditor->GetDocument(getter_AddRefs(domDoc));
|
|
NS_ENSURE_TRUE(domDoc, nullptr);
|
|
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
|
|
NS_ENSURE_TRUE(doc, nullptr);
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
|
|
return loadContext.forget();
|
|
}
|
|
|
|
/**
|
|
* Fetches the dictionary stored in content prefs and maintains state during the
|
|
* fetch, which is asynchronous.
|
|
*/
|
|
class DictionaryFetcher final : public nsIContentPrefCallback2
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
DictionaryFetcher(nsEditorSpellCheck* aSpellCheck,
|
|
nsIEditorSpellCheckCallback* aCallback,
|
|
uint32_t aGroup)
|
|
: mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {}
|
|
|
|
NS_IMETHOD Fetch(nsIEditor* aEditor);
|
|
|
|
NS_IMETHOD HandleResult(nsIContentPref* aPref) override
|
|
{
|
|
nsCOMPtr<nsIVariant> value;
|
|
nsresult rv = aPref->GetValue(getter_AddRefs(value));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
value->GetAsAString(mDictionary);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD HandleCompletion(uint16_t reason) override
|
|
{
|
|
mSpellCheck->DictionaryFetched(this);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD HandleError(nsresult error) override
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
|
|
uint32_t mGroup;
|
|
nsString mRootContentLang;
|
|
nsString mRootDocContentLang;
|
|
nsString mDictionary;
|
|
|
|
private:
|
|
~DictionaryFetcher() {}
|
|
|
|
nsRefPtr<nsEditorSpellCheck> mSpellCheck;
|
|
};
|
|
NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2)
|
|
|
|
NS_IMETHODIMP
|
|
DictionaryFetcher::Fetch(nsIEditor* aEditor)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aEditor);
|
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIURI> docUri;
|
|
rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString docUriSpec;
|
|
rv = docUri->GetSpec(docUriSpec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIContentPrefService2> contentPrefService =
|
|
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
|
|
rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec),
|
|
CPS_PREF_NAME, loadContext,
|
|
this);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Stores the current dictionary for aEditor's document URL.
|
|
*/
|
|
static nsresult
|
|
StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aEditor);
|
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIURI> docUri;
|
|
rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString docUriSpec;
|
|
rv = docUri->GetSpec(docUriSpec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
|
NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
|
|
prefValue->SetAsAString(aDictionary);
|
|
|
|
nsCOMPtr<nsIContentPrefService2> contentPrefService =
|
|
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
|
|
return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
|
|
CPS_PREF_NAME, prefValue, loadContext,
|
|
nullptr);
|
|
}
|
|
|
|
/**
|
|
* Forgets the current dictionary stored for aEditor's document URL.
|
|
*/
|
|
static nsresult
|
|
ClearCurrentDictionary(nsIEditor* aEditor)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aEditor);
|
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIURI> docUri;
|
|
rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString docUriSpec;
|
|
rv = docUri->GetSpec(docUriSpec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIContentPrefService2> contentPrefService =
|
|
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
|
|
return contentPrefService->RemoveByDomainAndName(
|
|
NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
|
|
NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
|
|
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck,
|
|
mEditor,
|
|
mSpellChecker,
|
|
mTxtSrvFilter)
|
|
|
|
nsEditorSpellCheck::nsEditorSpellCheck()
|
|
: mSuggestedWordIndex(0)
|
|
, mDictionaryIndex(0)
|
|
, mEditor(nullptr)
|
|
, mDictionaryFetcherGroup(0)
|
|
, mUpdateDictionaryRunning(false)
|
|
{
|
|
}
|
|
|
|
nsEditorSpellCheck::~nsEditorSpellCheck()
|
|
{
|
|
// Make sure we blow the spellchecker away, just in
|
|
// case it hasn't been destroyed already.
|
|
mSpellChecker = nullptr;
|
|
}
|
|
|
|
// The problem is that if the spell checker does not exist, we can not tell
|
|
// which dictionaries are installed. This function works around the problem,
|
|
// allowing callers to ask if we can spell check without actually doing so (and
|
|
// enabling or disabling UI as necessary). This just creates a spellcheck
|
|
// object if needed and asks it for the dictionary list.
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::CanSpellCheck(bool* _retval)
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsISpellChecker> spellChecker;
|
|
if (! mSpellChecker) {
|
|
spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
spellChecker = mSpellChecker;
|
|
}
|
|
nsTArray<nsString> dictList;
|
|
rv = spellChecker->GetDictionaryList(&dictList);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
*_retval = (dictList.Length() > 0);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Instances of this class can be used as either runnables or RAII helpers.
|
|
class CallbackCaller final : public nsRunnable
|
|
{
|
|
public:
|
|
explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
|
|
: mCallback(aCallback) {}
|
|
|
|
~CallbackCaller()
|
|
{
|
|
Run();
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
if (mCallback) {
|
|
mCallback->EditorSpellCheckDone();
|
|
mCallback = nullptr;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback)
|
|
{
|
|
NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
|
|
mEditor = aEditor;
|
|
|
|
nsresult rv;
|
|
|
|
// We can spell check with any editor type
|
|
nsCOMPtr<nsITextServicesDocument>tsDoc =
|
|
do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
|
|
|
|
tsDoc->SetFilter(mTxtSrvFilter);
|
|
|
|
// Pass the editor to the text services document
|
|
rv = tsDoc->InitWithEditor(aEditor);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (aEnableSelectionChecking) {
|
|
// Find out if the section is collapsed or not.
|
|
// If it isn't, we want to spellcheck just the selection.
|
|
|
|
nsCOMPtr<nsISelection> domSelection;
|
|
aEditor->GetSelection(getter_AddRefs(domSelection));
|
|
nsRefPtr<Selection> selection = static_cast<Selection*>(domSelection.get());
|
|
NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
|
|
|
|
int32_t count = 0;
|
|
|
|
rv = selection->GetRangeCount(&count);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (count > 0) {
|
|
nsRefPtr<nsRange> range = selection->GetRangeAt(0);
|
|
NS_ENSURE_STATE(range);
|
|
|
|
bool collapsed = false;
|
|
rv = range->GetCollapsed(&collapsed);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!collapsed) {
|
|
// We don't want to touch the range in the selection,
|
|
// so create a new copy of it.
|
|
|
|
nsRefPtr<nsRange> rangeBounds = range->CloneRange();
|
|
|
|
// Make sure the new range spans complete words.
|
|
|
|
rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Now tell the text services that you only want
|
|
// to iterate over the text in this range.
|
|
|
|
rv = tsDoc->SetExtent(rangeBounds);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
}
|
|
|
|
mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
|
|
|
|
rv = mSpellChecker->SetDocument(tsDoc, true);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// do not fail if UpdateCurrentDictionary fails because this method may
|
|
// succeed later.
|
|
rv = UpdateCurrentDictionary(aCallback);
|
|
if (NS_FAILED(rv) && aCallback) {
|
|
// However, if it does fail, we still need to call the callback since we
|
|
// discard the failure. Do it asynchronously so that the caller is always
|
|
// guaranteed async behavior.
|
|
nsRefPtr<CallbackCaller> caller = new CallbackCaller(aCallback);
|
|
NS_ENSURE_STATE(caller);
|
|
rv = NS_DispatchToMainThread(caller);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsAutoString nextMisspelledWord;
|
|
|
|
DeleteSuggestedWordList();
|
|
// Beware! This may flush notifications via synchronous
|
|
// ScrollSelectionIntoView.
|
|
nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord,
|
|
&mSuggestedWordList);
|
|
|
|
*aNextMisspelledWord = ToNewUnicode(nextMisspelledWord);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord)
|
|
{
|
|
nsAutoString word;
|
|
if ( mSuggestedWordIndex < int32_t(mSuggestedWordList.Length()))
|
|
{
|
|
*aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]);
|
|
mSuggestedWordIndex++;
|
|
} else {
|
|
// A blank string signals that there are no more strings
|
|
*aSuggestedWord = ToNewUnicode(EmptyString());
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord,
|
|
bool *aIsMisspelled)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
DeleteSuggestedWordList();
|
|
return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
|
|
aIsMisspelled, &mSuggestedWordList);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord,
|
|
bool *aIsMisspelled)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
|
|
aIsMisspelled, nullptr);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord,
|
|
const char16_t *aReplaceWord,
|
|
bool allOccurrences)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mSpellChecker->Replace(nsDependentString(aMisspelledWord),
|
|
nsDependentString(aReplaceWord), allOccurrences);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mSpellChecker->IgnoreAll(nsDependentString(aWord));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::GetPersonalDictionary()
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
// We can spell check with any editor type
|
|
mDictionaryList.Clear();
|
|
mDictionaryIndex = 0;
|
|
return mSpellChecker->GetPersonalDictionary(&mDictionaryList);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord)
|
|
{
|
|
if ( mDictionaryIndex < int32_t( mDictionaryList.Length()))
|
|
{
|
|
*aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]);
|
|
mDictionaryIndex++;
|
|
} else {
|
|
// A blank string signals that there are no more strings
|
|
*aDictionaryWord = ToNewUnicode(EmptyString());
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER);
|
|
|
|
*aDictionaryList = 0;
|
|
*aCount = 0;
|
|
|
|
nsTArray<nsString> dictList;
|
|
|
|
nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
char16_t **tmpPtr = 0;
|
|
|
|
if (dictList.Length() < 1)
|
|
{
|
|
// If there are no dictionaries, return an array containing
|
|
// one element and a count of one.
|
|
|
|
tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *));
|
|
|
|
NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
*tmpPtr = 0;
|
|
*aDictionaryList = tmpPtr;
|
|
*aCount = 0;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *) * dictList.Length());
|
|
|
|
NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
*aDictionaryList = tmpPtr;
|
|
*aCount = dictList.Length();
|
|
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < *aCount; i++)
|
|
{
|
|
tmpPtr[i] = ToNewUnicode(dictList[i]);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mSpellChecker->GetCurrentDictionary(aDictionary);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
|
|
|
|
// The purpose of mUpdateDictionaryRunning is to avoid doing all of this if
|
|
// UpdateCurrentDictionary's helper method DictionaryFetched, which calls us,
|
|
// is on the stack (meaning: only do this if the user manually selected a
|
|
// dictionary to use)
|
|
if (!mUpdateDictionaryRunning) {
|
|
|
|
// Ignore pending dictionary fetchers by increasing this number.
|
|
mDictionaryFetcherGroup++;
|
|
|
|
uint32_t flags = 0;
|
|
mEditor->GetFlags(&flags);
|
|
if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
|
|
if (mPreferredLang.IsEmpty() ||
|
|
!mPreferredLang.Equals(aDictionary, nsCaseInsensitiveStringComparator())) {
|
|
// When user sets dictionary manually, we store this value associated
|
|
// with editor url.
|
|
StoreCurrentDictionary(mEditor, aDictionary);
|
|
} else {
|
|
// If user sets a dictionary matching (even partially), lang defined by
|
|
// document, we consider content pref has been canceled, and we clear it.
|
|
ClearCurrentDictionary(mEditor);
|
|
}
|
|
|
|
// Also store it in as a preference. It will be used as a default value
|
|
// when everything else fails but we don't want this for mail composer
|
|
// because it has spellchecked dictionary settings in Preferences.
|
|
Preferences::SetString("spellchecker.dictionary", aDictionary);
|
|
}
|
|
}
|
|
return mSpellChecker->SetCurrentDictionary(aDictionary);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::CheckCurrentDictionary()
|
|
{
|
|
mSpellChecker->CheckCurrentDictionary();
|
|
|
|
// Check if our current dictionary is still available.
|
|
nsAutoString currentDictionary;
|
|
nsresult rv = GetCurrentDictionary(currentDictionary);
|
|
if (NS_SUCCEEDED(rv) && !currentDictionary.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// If our preferred current dictionary has gone, pick another one.
|
|
nsTArray<nsString> dictList;
|
|
rv = mSpellChecker->GetDictionaryList(&dictList);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (dictList.Length() > 0) {
|
|
rv = SetCurrentDictionary(dictList[0]);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::UninitSpellChecker()
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
// Cleanup - kill the spell checker
|
|
DeleteSuggestedWordList();
|
|
mDictionaryList.Clear();
|
|
mDictionaryIndex = 0;
|
|
mSpellChecker = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter)
|
|
{
|
|
mTxtSrvFilter = filter;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsEditorSpellCheck::DeleteSuggestedWordList()
|
|
{
|
|
mSuggestedWordList.Clear();
|
|
mSuggestedWordIndex = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
|
|
|
|
// Get language with html5 algorithm
|
|
nsCOMPtr<nsIContent> rootContent;
|
|
nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
|
|
if (htmlEditor) {
|
|
rootContent = htmlEditor->GetActiveEditingHost();
|
|
} else {
|
|
nsCOMPtr<nsIDOMElement> rootElement;
|
|
rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rootContent = do_QueryInterface(rootElement);
|
|
}
|
|
|
|
// Try to get topmost document's document element for embedded mail editor.
|
|
uint32_t flags = 0;
|
|
mEditor->GetFlags(&flags);
|
|
if (flags & nsIPlaintextEditor::eEditorMailMask) {
|
|
nsCOMPtr<nsIDocument> ownerDoc = rootContent->OwnerDoc();
|
|
NS_ENSURE_TRUE(ownerDoc, NS_ERROR_FAILURE);
|
|
nsIDocument* parentDoc = ownerDoc->GetParentDocument();
|
|
if (parentDoc) {
|
|
rootContent = do_QueryInterface(parentDoc->GetDocumentElement());
|
|
}
|
|
}
|
|
NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
|
|
|
|
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);
|
|
|
|
rv = fetcher->Fetch(mEditor);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
|
|
{
|
|
MOZ_ASSERT(aFetcher);
|
|
nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
|
|
|
|
// 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);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* We try to derive the dictionary to use based on the following priorities:
|
|
* 1) Content preference: the language the user set for the site before.
|
|
* 2) The value of "spellchecker.dictionary.override" which reflects a
|
|
* global choice of language explicitly set by the user.
|
|
* 3) Language set by the website, or any other dictionary that partly matches that.
|
|
* Eg. if the website is "en-GB", a user who only has "en-US" will get that.
|
|
* If the website is generic "en", the user will get one of the "en-*" installed,
|
|
* pretty much at random.
|
|
* 4) The user's locale
|
|
* 5) Leave the current dictionary set.
|
|
* 6) The content of the "LANG" environment variable (if set)
|
|
* 7) The first spell check dictionary installed.
|
|
*/
|
|
|
|
// Get the language from the element or its closest parent according to:
|
|
// https://html.spec.whatwg.org/#attr-lang
|
|
// This is used in SetCurrentDictionary.
|
|
mPreferredLang.Assign(aFetcher->mRootContentLang);
|
|
|
|
// If no luck, try the "Content-Language" header.
|
|
if (mPreferredLang.IsEmpty()) {
|
|
mPreferredLang.Assign(aFetcher->mRootDocContentLang);
|
|
}
|
|
|
|
// Priority 1:
|
|
// Get language from content prefs, if set.
|
|
// If we successfully fetched a dictionary from content prefs, do not go
|
|
// further. Use this exact dictionary.
|
|
nsAutoString dictName;
|
|
uint32_t flags;
|
|
mEditor->GetFlags(&flags);
|
|
if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
|
|
dictName.Assign(aFetcher->mDictionary);
|
|
if (!dictName.IsEmpty()) {
|
|
if (NS_SUCCEEDED(SetCurrentDictionary(dictName))) {
|
|
|
|
// We take an early exit here, so let's not forget to clear the word
|
|
// list.
|
|
DeleteSuggestedWordList();
|
|
return NS_OK;
|
|
}
|
|
// May be dictionary was uninstalled ?
|
|
// Clear the content preference and continue.
|
|
ClearCurrentDictionary(mEditor);
|
|
}
|
|
}
|
|
|
|
// Priority 2:
|
|
// Get global preferred language from preferences, if set.
|
|
nsAutoString preferredDict;
|
|
preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary.override");
|
|
if (!preferredDict.IsEmpty()) {
|
|
dictName.Assign(preferredDict);
|
|
}
|
|
|
|
// Priority 3:
|
|
// Get language from element/doc, if set.
|
|
if (dictName.IsEmpty() && !mPreferredLang.IsEmpty()) {
|
|
dictName.Assign(mPreferredLang);
|
|
}
|
|
|
|
// Auxiliary status value
|
|
nsresult rv2;
|
|
|
|
// Obtain a list of available dictionaries to check against.
|
|
nsTArray<nsString> dictList;
|
|
rv2 = mSpellChecker->GetDictionaryList(&dictList);
|
|
NS_ENSURE_SUCCESS(rv2, rv2);
|
|
int32_t i, dictCount = dictList.Length();
|
|
|
|
// The following will be driven by this status. Once we are able to set a
|
|
// dictionary successfully, we're done. So we start with a "failed" status.
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
|
|
if (!dictName.IsEmpty()) {
|
|
for (i = 0; i < dictCount; i++) {
|
|
nsAutoString dictStr(dictList.ElementAt(i));
|
|
if (dictName.Equals(dictStr, nsCaseInsensitiveStringComparator())) {
|
|
// First let's correct any problems due to non-matching case.
|
|
// This applies to both a user-set override and document language.
|
|
// RFC 5646 explicitly states that matches should be case-insensitive.
|
|
dictName.Assign(dictStr);
|
|
// Try to set it. Doing this inside this loop avoids trying to set a
|
|
// dictionary that isn't available.
|
|
rv = SetCurrentDictionary(dictName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
// Required dictionary was not available. Try to get a dictionary
|
|
// matching at least the language part of dictName:
|
|
nsAutoString langCode;
|
|
int32_t dashIdx = dictName.FindChar('-');
|
|
if (dashIdx != -1) {
|
|
langCode.Assign(Substring(dictName, 0, dashIdx));
|
|
} else {
|
|
langCode.Assign(dictName);
|
|
}
|
|
|
|
nsDefaultStringComparator comparator;
|
|
// Loop over available dictionaries; if we find one with the required
|
|
// language, use it.
|
|
for (i = 0; i < dictCount; i++) {
|
|
nsAutoString dictStr(dictList.ElementAt(i));
|
|
if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) &&
|
|
NS_SUCCEEDED(rv = SetCurrentDictionary(dictStr))) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Priority 4:
|
|
// Content prefs, override and document didn't give us a valid dictionary
|
|
// name, so we just get the current locale and try to use that.
|
|
if (NS_FAILED (rv)) {
|
|
nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
|
|
mozilla::services::GetXULChromeRegistryService();
|
|
|
|
if (packageRegistry) {
|
|
nsAutoCString utf8DictName;
|
|
rv2 = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"),
|
|
utf8DictName);
|
|
dictName.Assign(EmptyString());
|
|
AppendUTF8toUTF16(utf8DictName, dictName);
|
|
// Try to set it, if it's in the list.
|
|
for (i = 0; i < dictCount; i++) {
|
|
nsAutoString dictStr(dictList.ElementAt(i));
|
|
if (dictStr.Equals(dictName)) {
|
|
rv = SetCurrentDictionary(dictName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
// Still no success. Further fallback attempts required.
|
|
|
|
// Priority 5:
|
|
// If we have a current dictionary, don't try anything else.
|
|
nsAutoString currentDictionary;
|
|
rv2 = GetCurrentDictionary(currentDictionary);
|
|
|
|
if (NS_FAILED(rv2) || currentDictionary.IsEmpty()) {
|
|
// We don't have a current dictionary.
|
|
// Priority 6:
|
|
// Try to get current dictionary from environment variable LANG.
|
|
// LANG = language[_territory][.codeset]
|
|
char* env_lang = getenv("LANG");
|
|
if (env_lang != nullptr) {
|
|
nsString lang = NS_ConvertUTF8toUTF16(env_lang);
|
|
|
|
// Strip trailing charset, if there is any.
|
|
int32_t dot_pos = lang.FindChar('.');
|
|
if (dot_pos != -1) {
|
|
lang = Substring(lang, 0, dot_pos);
|
|
}
|
|
|
|
// Convert underscore to dash.
|
|
int32_t underScore = lang.FindChar('_');
|
|
if (underScore != -1) {
|
|
lang.Replace(underScore, 1, '-');
|
|
// Only attempt to set if a _territory is present.
|
|
rv = SetCurrentDictionary(lang);
|
|
}
|
|
}
|
|
|
|
// Priority 7:
|
|
// If LANG does not work either, pick the first one.
|
|
if (NS_FAILED(rv)) {
|
|
if (dictList.Length() > 0) {
|
|
rv = SetCurrentDictionary(dictList[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If an error was thrown while setting the dictionary, just
|
|
// fail silently so that the spellchecker dialog is allowed to come
|
|
// up. The user can manually reset the language to their choice on
|
|
// the dialog if it is wrong.
|
|
|
|
// Dictionary has changed, so delete the suggested word list.
|
|
DeleteSuggestedWordList();
|
|
|
|
return NS_OK;
|
|
}
|