1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/composer/src/nsEditorSpellCheck.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,871 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include <stdlib.h> // for getenv 1.10 + 1.11 +#include "mozilla/Attributes.h" // for MOZ_FINAL 1.12 +#include "mozilla/Preferences.h" // for Preferences 1.13 +#include "mozilla/Services.h" // for GetXULChromeRegistryService 1.14 +#include "mozilla/dom/Element.h" // for Element 1.15 +#include "mozilla/mozalloc.h" // for operator delete, etc 1.16 +#include "nsAString.h" // for nsAString_internal::IsEmpty, etc 1.17 +#include "nsComponentManagerUtils.h" // for do_CreateInstance 1.18 +#include "nsDebug.h" // for NS_ENSURE_TRUE, etc 1.19 +#include "nsDependentSubstring.h" // for Substring 1.20 +#include "nsEditorSpellCheck.h" 1.21 +#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc 1.22 +#include "nsIChromeRegistry.h" // for nsIXULChromeRegistry 1.23 +#include "nsIContent.h" // for nsIContent 1.24 +#include "nsIContentPrefService.h" // for nsIContentPrefService, etc 1.25 +#include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc 1.26 +#include "nsIDOMDocument.h" // for nsIDOMDocument 1.27 +#include "nsIDOMElement.h" // for nsIDOMElement 1.28 +#include "nsIDOMRange.h" // for nsIDOMRange 1.29 +#include "nsIDocument.h" // for nsIDocument 1.30 +#include "nsIEditor.h" // for nsIEditor 1.31 +#include "nsIHTMLEditor.h" // for nsIHTMLEditor 1.32 +#include "nsILoadContext.h" 1.33 +#include "nsISelection.h" // for nsISelection 1.34 +#include "nsISpellChecker.h" // for nsISpellChecker, etc 1.35 +#include "nsISupportsBase.h" // for nsISupports 1.36 +#include "nsISupportsUtils.h" // for NS_ADDREF 1.37 +#include "nsITextServicesDocument.h" // for nsITextServicesDocument 1.38 +#include "nsITextServicesFilter.h" // for nsITextServicesFilter 1.39 +#include "nsIURI.h" // for nsIURI 1.40 +#include "nsIVariant.h" // for nsIWritableVariant, etc 1.41 +#include "nsLiteralString.h" // for NS_LITERAL_STRING, etc 1.42 +#include "nsMemory.h" // for nsMemory 1.43 +#include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc 1.44 +#include "nsServiceManagerUtils.h" // for do_GetService 1.45 +#include "nsString.h" // for nsAutoString, nsString, etc 1.46 +#include "nsStringFwd.h" // for nsAFlatString 1.47 +#include "nsStyleUtil.h" // for nsStyleUtil 1.48 + 1.49 +using namespace mozilla; 1.50 + 1.51 +class UpdateDictionnaryHolder { 1.52 + private: 1.53 + nsEditorSpellCheck* mSpellCheck; 1.54 + public: 1.55 + UpdateDictionnaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) { 1.56 + if (mSpellCheck) { 1.57 + mSpellCheck->BeginUpdateDictionary(); 1.58 + } 1.59 + } 1.60 + ~UpdateDictionnaryHolder() { 1.61 + if (mSpellCheck) { 1.62 + mSpellCheck->EndUpdateDictionary(); 1.63 + } 1.64 + } 1.65 +}; 1.66 + 1.67 +#define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang") 1.68 + 1.69 +/** 1.70 + * Gets the URI of aEditor's document. 1.71 + */ 1.72 +static nsresult 1.73 +GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI) 1.74 +{ 1.75 + NS_ENSURE_ARG_POINTER(aEditor); 1.76 + NS_ENSURE_ARG_POINTER(aURI); 1.77 + 1.78 + nsCOMPtr<nsIDOMDocument> domDoc; 1.79 + aEditor->GetDocument(getter_AddRefs(domDoc)); 1.80 + NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); 1.81 + 1.82 + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); 1.83 + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); 1.84 + 1.85 + nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI(); 1.86 + NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE); 1.87 + 1.88 + *aURI = docUri; 1.89 + NS_ADDREF(*aURI); 1.90 + return NS_OK; 1.91 +} 1.92 + 1.93 +static already_AddRefed<nsILoadContext> 1.94 +GetLoadContext(nsIEditor* aEditor) 1.95 +{ 1.96 + nsCOMPtr<nsIDOMDocument> domDoc; 1.97 + aEditor->GetDocument(getter_AddRefs(domDoc)); 1.98 + NS_ENSURE_TRUE(domDoc, nullptr); 1.99 + 1.100 + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); 1.101 + NS_ENSURE_TRUE(doc, nullptr); 1.102 + 1.103 + nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext(); 1.104 + return loadContext.forget(); 1.105 +} 1.106 + 1.107 +/** 1.108 + * Fetches the dictionary stored in content prefs and maintains state during the 1.109 + * fetch, which is asynchronous. 1.110 + */ 1.111 +class DictionaryFetcher MOZ_FINAL : public nsIContentPrefCallback2 1.112 +{ 1.113 +public: 1.114 + NS_DECL_ISUPPORTS 1.115 + 1.116 + DictionaryFetcher(nsEditorSpellCheck* aSpellCheck, 1.117 + nsIEditorSpellCheckCallback* aCallback, 1.118 + uint32_t aGroup) 1.119 + : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {} 1.120 + 1.121 + NS_IMETHOD Fetch(nsIEditor* aEditor); 1.122 + 1.123 + NS_IMETHOD HandleResult(nsIContentPref* aPref) 1.124 + { 1.125 + nsCOMPtr<nsIVariant> value; 1.126 + nsresult rv = aPref->GetValue(getter_AddRefs(value)); 1.127 + NS_ENSURE_SUCCESS(rv, rv); 1.128 + value->GetAsAString(mDictionary); 1.129 + return NS_OK; 1.130 + } 1.131 + 1.132 + NS_IMETHOD HandleCompletion(uint16_t reason) 1.133 + { 1.134 + mSpellCheck->DictionaryFetched(this); 1.135 + return NS_OK; 1.136 + } 1.137 + 1.138 + NS_IMETHOD HandleError(nsresult error) 1.139 + { 1.140 + return NS_OK; 1.141 + } 1.142 + 1.143 + nsCOMPtr<nsIEditorSpellCheckCallback> mCallback; 1.144 + uint32_t mGroup; 1.145 + nsString mRootContentLang; 1.146 + nsString mRootDocContentLang; 1.147 + nsString mDictionary; 1.148 + 1.149 +private: 1.150 + nsRefPtr<nsEditorSpellCheck> mSpellCheck; 1.151 +}; 1.152 +NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2) 1.153 + 1.154 +NS_IMETHODIMP 1.155 +DictionaryFetcher::Fetch(nsIEditor* aEditor) 1.156 +{ 1.157 + NS_ENSURE_ARG_POINTER(aEditor); 1.158 + 1.159 + nsresult rv; 1.160 + 1.161 + nsCOMPtr<nsIURI> docUri; 1.162 + rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); 1.163 + NS_ENSURE_SUCCESS(rv, rv); 1.164 + 1.165 + nsAutoCString docUriSpec; 1.166 + rv = docUri->GetSpec(docUriSpec); 1.167 + NS_ENSURE_SUCCESS(rv, rv); 1.168 + 1.169 + nsCOMPtr<nsIContentPrefService2> contentPrefService = 1.170 + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); 1.171 + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE); 1.172 + 1.173 + nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor); 1.174 + rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec), 1.175 + CPS_PREF_NAME, loadContext, 1.176 + this); 1.177 + NS_ENSURE_SUCCESS(rv, rv); 1.178 + 1.179 + return NS_OK; 1.180 +} 1.181 + 1.182 +/** 1.183 + * Stores the current dictionary for aEditor's document URL. 1.184 + */ 1.185 +static nsresult 1.186 +StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary) 1.187 +{ 1.188 + NS_ENSURE_ARG_POINTER(aEditor); 1.189 + 1.190 + nsresult rv; 1.191 + 1.192 + nsCOMPtr<nsIURI> docUri; 1.193 + rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); 1.194 + NS_ENSURE_SUCCESS(rv, rv); 1.195 + 1.196 + nsAutoCString docUriSpec; 1.197 + rv = docUri->GetSpec(docUriSpec); 1.198 + NS_ENSURE_SUCCESS(rv, rv); 1.199 + 1.200 + nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID); 1.201 + NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY); 1.202 + prefValue->SetAsAString(aDictionary); 1.203 + 1.204 + nsCOMPtr<nsIContentPrefService2> contentPrefService = 1.205 + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); 1.206 + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); 1.207 + 1.208 + nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor); 1.209 + return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec), 1.210 + CPS_PREF_NAME, prefValue, loadContext, 1.211 + nullptr); 1.212 +} 1.213 + 1.214 +/** 1.215 + * Forgets the current dictionary stored for aEditor's document URL. 1.216 + */ 1.217 +static nsresult 1.218 +ClearCurrentDictionary(nsIEditor* aEditor) 1.219 +{ 1.220 + NS_ENSURE_ARG_POINTER(aEditor); 1.221 + 1.222 + nsresult rv; 1.223 + 1.224 + nsCOMPtr<nsIURI> docUri; 1.225 + rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); 1.226 + NS_ENSURE_SUCCESS(rv, rv); 1.227 + 1.228 + nsAutoCString docUriSpec; 1.229 + rv = docUri->GetSpec(docUriSpec); 1.230 + NS_ENSURE_SUCCESS(rv, rv); 1.231 + 1.232 + nsCOMPtr<nsIContentPrefService2> contentPrefService = 1.233 + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); 1.234 + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); 1.235 + 1.236 + nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor); 1.237 + return contentPrefService->RemoveByDomainAndName( 1.238 + NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr); 1.239 +} 1.240 + 1.241 +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck) 1.242 +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck) 1.243 + 1.244 +NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck) 1.245 + NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck) 1.246 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck) 1.247 + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck) 1.248 +NS_INTERFACE_MAP_END 1.249 + 1.250 +NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck, 1.251 + mEditor, 1.252 + mSpellChecker, 1.253 + mTxtSrvFilter) 1.254 + 1.255 +nsEditorSpellCheck::nsEditorSpellCheck() 1.256 + : mSuggestedWordIndex(0) 1.257 + , mDictionaryIndex(0) 1.258 + , mEditor(nullptr) 1.259 + , mDictionaryFetcherGroup(0) 1.260 + , mUpdateDictionaryRunning(false) 1.261 +{ 1.262 +} 1.263 + 1.264 +nsEditorSpellCheck::~nsEditorSpellCheck() 1.265 +{ 1.266 + // Make sure we blow the spellchecker away, just in 1.267 + // case it hasn't been destroyed already. 1.268 + mSpellChecker = nullptr; 1.269 +} 1.270 + 1.271 +// The problem is that if the spell checker does not exist, we can not tell 1.272 +// which dictionaries are installed. This function works around the problem, 1.273 +// allowing callers to ask if we can spell check without actually doing so (and 1.274 +// enabling or disabling UI as necessary). This just creates a spellcheck 1.275 +// object if needed and asks it for the dictionary list. 1.276 +NS_IMETHODIMP 1.277 +nsEditorSpellCheck::CanSpellCheck(bool* _retval) 1.278 +{ 1.279 + nsresult rv; 1.280 + nsCOMPtr<nsISpellChecker> spellChecker; 1.281 + if (! mSpellChecker) { 1.282 + spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); 1.283 + NS_ENSURE_SUCCESS(rv, rv); 1.284 + } else { 1.285 + spellChecker = mSpellChecker; 1.286 + } 1.287 + nsTArray<nsString> dictList; 1.288 + rv = spellChecker->GetDictionaryList(&dictList); 1.289 + NS_ENSURE_SUCCESS(rv, rv); 1.290 + 1.291 + *_retval = (dictList.Length() > 0); 1.292 + return NS_OK; 1.293 +} 1.294 + 1.295 +// Instances of this class can be used as either runnables or RAII helpers. 1.296 +class CallbackCaller MOZ_FINAL : public nsRunnable 1.297 +{ 1.298 +public: 1.299 + explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback) 1.300 + : mCallback(aCallback) {} 1.301 + 1.302 + ~CallbackCaller() 1.303 + { 1.304 + Run(); 1.305 + } 1.306 + 1.307 + NS_IMETHOD Run() 1.308 + { 1.309 + if (mCallback) { 1.310 + mCallback->EditorSpellCheckDone(); 1.311 + mCallback = nullptr; 1.312 + } 1.313 + return NS_OK; 1.314 + } 1.315 + 1.316 +private: 1.317 + nsCOMPtr<nsIEditorSpellCheckCallback> mCallback; 1.318 +}; 1.319 + 1.320 +NS_IMETHODIMP 1.321 +nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback) 1.322 +{ 1.323 + NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER); 1.324 + mEditor = aEditor; 1.325 + 1.326 + nsresult rv; 1.327 + 1.328 + // We can spell check with any editor type 1.329 + nsCOMPtr<nsITextServicesDocument>tsDoc = 1.330 + do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv); 1.331 + NS_ENSURE_SUCCESS(rv, rv); 1.332 + 1.333 + NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER); 1.334 + 1.335 + tsDoc->SetFilter(mTxtSrvFilter); 1.336 + 1.337 + // Pass the editor to the text services document 1.338 + rv = tsDoc->InitWithEditor(aEditor); 1.339 + NS_ENSURE_SUCCESS(rv, rv); 1.340 + 1.341 + if (aEnableSelectionChecking) { 1.342 + // Find out if the section is collapsed or not. 1.343 + // If it isn't, we want to spellcheck just the selection. 1.344 + 1.345 + nsCOMPtr<nsISelection> selection; 1.346 + 1.347 + rv = aEditor->GetSelection(getter_AddRefs(selection)); 1.348 + NS_ENSURE_SUCCESS(rv, rv); 1.349 + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); 1.350 + 1.351 + int32_t count = 0; 1.352 + 1.353 + rv = selection->GetRangeCount(&count); 1.354 + NS_ENSURE_SUCCESS(rv, rv); 1.355 + 1.356 + if (count > 0) { 1.357 + nsCOMPtr<nsIDOMRange> range; 1.358 + 1.359 + rv = selection->GetRangeAt(0, getter_AddRefs(range)); 1.360 + NS_ENSURE_SUCCESS(rv, rv); 1.361 + 1.362 + bool collapsed = false; 1.363 + rv = range->GetCollapsed(&collapsed); 1.364 + NS_ENSURE_SUCCESS(rv, rv); 1.365 + 1.366 + if (!collapsed) { 1.367 + // We don't want to touch the range in the selection, 1.368 + // so create a new copy of it. 1.369 + 1.370 + nsCOMPtr<nsIDOMRange> rangeBounds; 1.371 + rv = range->CloneRange(getter_AddRefs(rangeBounds)); 1.372 + NS_ENSURE_SUCCESS(rv, rv); 1.373 + NS_ENSURE_TRUE(rangeBounds, NS_ERROR_FAILURE); 1.374 + 1.375 + // Make sure the new range spans complete words. 1.376 + 1.377 + rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds); 1.378 + NS_ENSURE_SUCCESS(rv, rv); 1.379 + 1.380 + // Now tell the text services that you only want 1.381 + // to iterate over the text in this range. 1.382 + 1.383 + rv = tsDoc->SetExtent(rangeBounds); 1.384 + NS_ENSURE_SUCCESS(rv, rv); 1.385 + } 1.386 + } 1.387 + } 1.388 + 1.389 + mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); 1.390 + NS_ENSURE_SUCCESS(rv, rv); 1.391 + 1.392 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER); 1.393 + 1.394 + rv = mSpellChecker->SetDocument(tsDoc, true); 1.395 + NS_ENSURE_SUCCESS(rv, rv); 1.396 + 1.397 + // do not fail if UpdateCurrentDictionary fails because this method may 1.398 + // succeed later. 1.399 + rv = UpdateCurrentDictionary(aCallback); 1.400 + if (NS_FAILED(rv) && aCallback) { 1.401 + // However, if it does fail, we still need to call the callback since we 1.402 + // discard the failure. Do it asynchronously so that the caller is always 1.403 + // guaranteed async behavior. 1.404 + nsRefPtr<CallbackCaller> caller = new CallbackCaller(aCallback); 1.405 + NS_ENSURE_STATE(caller); 1.406 + rv = NS_DispatchToMainThread(caller); 1.407 + NS_ENSURE_SUCCESS(rv, rv); 1.408 + } 1.409 + 1.410 + return NS_OK; 1.411 +} 1.412 + 1.413 +NS_IMETHODIMP 1.414 +nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord) 1.415 +{ 1.416 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.417 + 1.418 + nsAutoString nextMisspelledWord; 1.419 + 1.420 + DeleteSuggestedWordList(); 1.421 + // Beware! This may flush notifications via synchronous 1.422 + // ScrollSelectionIntoView. 1.423 + nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord, 1.424 + &mSuggestedWordList); 1.425 + 1.426 + *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord); 1.427 + return rv; 1.428 +} 1.429 + 1.430 +NS_IMETHODIMP 1.431 +nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord) 1.432 +{ 1.433 + nsAutoString word; 1.434 + if ( mSuggestedWordIndex < int32_t(mSuggestedWordList.Length())) 1.435 + { 1.436 + *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]); 1.437 + mSuggestedWordIndex++; 1.438 + } else { 1.439 + // A blank string signals that there are no more strings 1.440 + *aSuggestedWord = ToNewUnicode(EmptyString()); 1.441 + } 1.442 + return NS_OK; 1.443 +} 1.444 + 1.445 +NS_IMETHODIMP 1.446 +nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord, 1.447 + bool *aIsMisspelled) 1.448 +{ 1.449 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.450 + 1.451 + DeleteSuggestedWordList(); 1.452 + return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), 1.453 + aIsMisspelled, &mSuggestedWordList); 1.454 +} 1.455 + 1.456 +NS_IMETHODIMP 1.457 +nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord, 1.458 + bool *aIsMisspelled) 1.459 +{ 1.460 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.461 + 1.462 + return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), 1.463 + aIsMisspelled, nullptr); 1.464 +} 1.465 + 1.466 +NS_IMETHODIMP 1.467 +nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord, 1.468 + const char16_t *aReplaceWord, 1.469 + bool allOccurrences) 1.470 +{ 1.471 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.472 + 1.473 + return mSpellChecker->Replace(nsDependentString(aMisspelledWord), 1.474 + nsDependentString(aReplaceWord), allOccurrences); 1.475 +} 1.476 + 1.477 +NS_IMETHODIMP 1.478 +nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord) 1.479 +{ 1.480 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.481 + 1.482 + return mSpellChecker->IgnoreAll(nsDependentString(aWord)); 1.483 +} 1.484 + 1.485 +NS_IMETHODIMP 1.486 +nsEditorSpellCheck::GetPersonalDictionary() 1.487 +{ 1.488 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.489 + 1.490 + // We can spell check with any editor type 1.491 + mDictionaryList.Clear(); 1.492 + mDictionaryIndex = 0; 1.493 + return mSpellChecker->GetPersonalDictionary(&mDictionaryList); 1.494 +} 1.495 + 1.496 +NS_IMETHODIMP 1.497 +nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord) 1.498 +{ 1.499 + if ( mDictionaryIndex < int32_t( mDictionaryList.Length())) 1.500 + { 1.501 + *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]); 1.502 + mDictionaryIndex++; 1.503 + } else { 1.504 + // A blank string signals that there are no more strings 1.505 + *aDictionaryWord = ToNewUnicode(EmptyString()); 1.506 + } 1.507 + 1.508 + return NS_OK; 1.509 +} 1.510 + 1.511 +NS_IMETHODIMP 1.512 +nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord) 1.513 +{ 1.514 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.515 + 1.516 + return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord)); 1.517 +} 1.518 + 1.519 +NS_IMETHODIMP 1.520 +nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord) 1.521 +{ 1.522 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.523 + 1.524 + return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord)); 1.525 +} 1.526 + 1.527 +NS_IMETHODIMP 1.528 +nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount) 1.529 +{ 1.530 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.531 + 1.532 + NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER); 1.533 + 1.534 + *aDictionaryList = 0; 1.535 + *aCount = 0; 1.536 + 1.537 + nsTArray<nsString> dictList; 1.538 + 1.539 + nsresult rv = mSpellChecker->GetDictionaryList(&dictList); 1.540 + 1.541 + NS_ENSURE_SUCCESS(rv, rv); 1.542 + 1.543 + char16_t **tmpPtr = 0; 1.544 + 1.545 + if (dictList.Length() < 1) 1.546 + { 1.547 + // If there are no dictionaries, return an array containing 1.548 + // one element and a count of one. 1.549 + 1.550 + tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *)); 1.551 + 1.552 + NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); 1.553 + 1.554 + *tmpPtr = 0; 1.555 + *aDictionaryList = tmpPtr; 1.556 + *aCount = 0; 1.557 + 1.558 + return NS_OK; 1.559 + } 1.560 + 1.561 + tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *) * dictList.Length()); 1.562 + 1.563 + NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); 1.564 + 1.565 + *aDictionaryList = tmpPtr; 1.566 + *aCount = dictList.Length(); 1.567 + 1.568 + uint32_t i; 1.569 + 1.570 + for (i = 0; i < *aCount; i++) 1.571 + { 1.572 + tmpPtr[i] = ToNewUnicode(dictList[i]); 1.573 + } 1.574 + 1.575 + return rv; 1.576 +} 1.577 + 1.578 +NS_IMETHODIMP 1.579 +nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary) 1.580 +{ 1.581 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.582 + 1.583 + return mSpellChecker->GetCurrentDictionary(aDictionary); 1.584 +} 1.585 + 1.586 +NS_IMETHODIMP 1.587 +nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary) 1.588 +{ 1.589 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.590 + 1.591 + nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this; 1.592 + 1.593 + // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if 1.594 + // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us, 1.595 + // is on the stack. 1.596 + if (!mUpdateDictionaryRunning) { 1.597 + 1.598 + // Ignore pending dictionary fetchers by increasing this number. 1.599 + mDictionaryFetcherGroup++; 1.600 + 1.601 + nsDefaultStringComparator comparator; 1.602 + nsAutoString langCode; 1.603 + int32_t dashIdx = aDictionary.FindChar('-'); 1.604 + if (dashIdx != -1) { 1.605 + langCode.Assign(Substring(aDictionary, 0, dashIdx)); 1.606 + } else { 1.607 + langCode.Assign(aDictionary); 1.608 + } 1.609 + 1.610 + if (mPreferredLang.IsEmpty() || !nsStyleUtil::DashMatchCompare(mPreferredLang, langCode, comparator)) { 1.611 + // When user sets dictionary manually, we store this value associated 1.612 + // with editor url. 1.613 + StoreCurrentDictionary(mEditor, aDictionary); 1.614 + } else { 1.615 + // If user sets a dictionary matching (even partially), lang defined by 1.616 + // document, we consider content pref has been canceled, and we clear it. 1.617 + ClearCurrentDictionary(mEditor); 1.618 + } 1.619 + 1.620 + // Also store it in as a preference. It will be used as a default value 1.621 + // when everything else fails. 1.622 + Preferences::SetString("spellchecker.dictionary", aDictionary); 1.623 + } 1.624 + return mSpellChecker->SetCurrentDictionary(aDictionary); 1.625 +} 1.626 + 1.627 +NS_IMETHODIMP 1.628 +nsEditorSpellCheck::CheckCurrentDictionary() 1.629 +{ 1.630 + mSpellChecker->CheckCurrentDictionary(); 1.631 + 1.632 + // Check if our current dictionary is still available. 1.633 + nsAutoString currentDictionary; 1.634 + nsresult rv = GetCurrentDictionary(currentDictionary); 1.635 + if (NS_SUCCEEDED(rv) && !currentDictionary.IsEmpty()) { 1.636 + return NS_OK; 1.637 + } 1.638 + 1.639 + // If our preferred current dictionary has gone, pick another one. 1.640 + nsTArray<nsString> dictList; 1.641 + rv = mSpellChecker->GetDictionaryList(&dictList); 1.642 + NS_ENSURE_SUCCESS(rv, rv); 1.643 + 1.644 + if (dictList.Length() > 0) { 1.645 + rv = SetCurrentDictionary(dictList[0]); 1.646 + NS_ENSURE_SUCCESS(rv, rv); 1.647 + } 1.648 + 1.649 + return NS_OK; 1.650 +} 1.651 + 1.652 +NS_IMETHODIMP 1.653 +nsEditorSpellCheck::UninitSpellChecker() 1.654 +{ 1.655 + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); 1.656 + 1.657 + // Cleanup - kill the spell checker 1.658 + DeleteSuggestedWordList(); 1.659 + mDictionaryList.Clear(); 1.660 + mDictionaryIndex = 0; 1.661 + mSpellChecker = 0; 1.662 + return NS_OK; 1.663 +} 1.664 + 1.665 + 1.666 +/* void setFilter (in nsITextServicesFilter filter); */ 1.667 +NS_IMETHODIMP 1.668 +nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter) 1.669 +{ 1.670 + mTxtSrvFilter = filter; 1.671 + return NS_OK; 1.672 +} 1.673 + 1.674 +nsresult 1.675 +nsEditorSpellCheck::DeleteSuggestedWordList() 1.676 +{ 1.677 + mSuggestedWordList.Clear(); 1.678 + mSuggestedWordIndex = 0; 1.679 + return NS_OK; 1.680 +} 1.681 + 1.682 +NS_IMETHODIMP 1.683 +nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback) 1.684 +{ 1.685 + nsresult rv; 1.686 + 1.687 + nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this; 1.688 + 1.689 + // Get language with html5 algorithm 1.690 + nsCOMPtr<nsIContent> rootContent; 1.691 + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor); 1.692 + if (htmlEditor) { 1.693 + rootContent = htmlEditor->GetActiveEditingHost(); 1.694 + } else { 1.695 + nsCOMPtr<nsIDOMElement> rootElement; 1.696 + rv = mEditor->GetRootElement(getter_AddRefs(rootElement)); 1.697 + NS_ENSURE_SUCCESS(rv, rv); 1.698 + rootContent = do_QueryInterface(rootElement); 1.699 + } 1.700 + NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE); 1.701 + 1.702 + DictionaryFetcher* fetcher = new DictionaryFetcher(this, aCallback, 1.703 + mDictionaryFetcherGroup); 1.704 + rootContent->GetLang(fetcher->mRootContentLang); 1.705 + nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc(); 1.706 + NS_ENSURE_STATE(doc); 1.707 + doc->GetContentLanguage(fetcher->mRootDocContentLang); 1.708 + 1.709 + rv = fetcher->Fetch(mEditor); 1.710 + NS_ENSURE_SUCCESS(rv, rv); 1.711 + 1.712 + return NS_OK; 1.713 +} 1.714 + 1.715 +nsresult 1.716 +nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) 1.717 +{ 1.718 + nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this; 1.719 + 1.720 + nsresult rv = NS_OK; 1.721 + 1.722 + // Important: declare the holder after the callback caller so that the former 1.723 + // is destructed first so that it's not active when the callback is called. 1.724 + CallbackCaller callbackCaller(aFetcher->mCallback); 1.725 + UpdateDictionnaryHolder holder(this); 1.726 + 1.727 + if (aFetcher->mGroup < mDictionaryFetcherGroup) { 1.728 + // SetCurrentDictionary was called after the fetch started. Don't overwrite 1.729 + // that dictionary with the fetched one. 1.730 + return NS_OK; 1.731 + } 1.732 + 1.733 + mPreferredLang.Assign(aFetcher->mRootContentLang); 1.734 + 1.735 + // If we successfully fetched a dictionary from content prefs, do not go 1.736 + // further. Use this exact dictionary. 1.737 + nsAutoString dictName; 1.738 + dictName.Assign(aFetcher->mDictionary); 1.739 + if (!dictName.IsEmpty()) { 1.740 + if (NS_FAILED(SetCurrentDictionary(dictName))) { 1.741 + // may be dictionary was uninstalled ? 1.742 + ClearCurrentDictionary(mEditor); 1.743 + } 1.744 + return NS_OK; 1.745 + } 1.746 + 1.747 + if (mPreferredLang.IsEmpty()) { 1.748 + mPreferredLang.Assign(aFetcher->mRootDocContentLang); 1.749 + } 1.750 + 1.751 + // Then, try to use language computed from element 1.752 + if (!mPreferredLang.IsEmpty()) { 1.753 + dictName.Assign(mPreferredLang); 1.754 + } 1.755 + 1.756 + // otherwise, get language from preferences 1.757 + nsAutoString preferedDict(Preferences::GetLocalizedString("spellchecker.dictionary")); 1.758 + if (dictName.IsEmpty()) { 1.759 + dictName.Assign(preferedDict); 1.760 + } 1.761 + 1.762 + if (dictName.IsEmpty()) 1.763 + { 1.764 + // Prefs didn't give us a dictionary name, so just get the current 1.765 + // locale and use that as the default dictionary name! 1.766 + 1.767 + nsCOMPtr<nsIXULChromeRegistry> packageRegistry = 1.768 + mozilla::services::GetXULChromeRegistryService(); 1.769 + 1.770 + if (packageRegistry) { 1.771 + nsAutoCString utf8DictName; 1.772 + rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"), 1.773 + utf8DictName); 1.774 + AppendUTF8toUTF16(utf8DictName, dictName); 1.775 + } 1.776 + } 1.777 + 1.778 + if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) { 1.779 + rv = SetCurrentDictionary(dictName); 1.780 + if (NS_FAILED(rv)) { 1.781 + // required dictionary was not available. Try to get a dictionary 1.782 + // matching at least language part of dictName: 1.783 + 1.784 + nsAutoString langCode; 1.785 + int32_t dashIdx = dictName.FindChar('-'); 1.786 + if (dashIdx != -1) { 1.787 + langCode.Assign(Substring(dictName, 0, dashIdx)); 1.788 + } else { 1.789 + langCode.Assign(dictName); 1.790 + } 1.791 + 1.792 + nsDefaultStringComparator comparator; 1.793 + 1.794 + // try dictionary.spellchecker preference if it starts with langCode (and 1.795 + // if we haven't tried it already) 1.796 + if (!preferedDict.IsEmpty() && !dictName.Equals(preferedDict) && 1.797 + nsStyleUtil::DashMatchCompare(preferedDict, langCode, comparator)) { 1.798 + rv = SetCurrentDictionary(preferedDict); 1.799 + } 1.800 + 1.801 + // Otherwise, try langCode (if we haven't tried it already) 1.802 + if (NS_FAILED(rv)) { 1.803 + if (!dictName.Equals(langCode) && !preferedDict.Equals(langCode)) { 1.804 + rv = SetCurrentDictionary(langCode); 1.805 + } 1.806 + } 1.807 + 1.808 + // Otherwise, try any available dictionary aa-XX 1.809 + if (NS_FAILED(rv)) { 1.810 + // loop over avaible dictionaries; if we find one with required 1.811 + // language, use it 1.812 + nsTArray<nsString> dictList; 1.813 + rv = mSpellChecker->GetDictionaryList(&dictList); 1.814 + NS_ENSURE_SUCCESS(rv, rv); 1.815 + int32_t i, count = dictList.Length(); 1.816 + for (i = 0; i < count; i++) { 1.817 + nsAutoString dictStr(dictList.ElementAt(i)); 1.818 + 1.819 + if (dictStr.Equals(dictName) || 1.820 + dictStr.Equals(preferedDict) || 1.821 + dictStr.Equals(langCode)) { 1.822 + // We have already tried it 1.823 + continue; 1.824 + } 1.825 + 1.826 + if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) && 1.827 + NS_SUCCEEDED(SetCurrentDictionary(dictStr))) { 1.828 + break; 1.829 + } 1.830 + } 1.831 + } 1.832 + } 1.833 + } 1.834 + 1.835 + // If we have not set dictionary, and the editable element doesn't have a 1.836 + // lang attribute, we try to get a dictionary. First try LANG environment variable, 1.837 + // then en-US. If it does not work, pick the first one. 1.838 + if (mPreferredLang.IsEmpty()) { 1.839 + nsAutoString currentDictionary; 1.840 + rv = GetCurrentDictionary(currentDictionary); 1.841 + if (NS_FAILED(rv) || currentDictionary.IsEmpty()) { 1.842 + // Try to get current dictionary from environment variable LANG 1.843 + char* env_lang = getenv("LANG"); 1.844 + if (env_lang != nullptr) { 1.845 + nsString lang = NS_ConvertUTF8toUTF16(env_lang); 1.846 + // Strip trailing charset if there is any 1.847 + int32_t dot_pos = lang.FindChar('.'); 1.848 + if (dot_pos != -1) { 1.849 + lang = Substring(lang, 0, dot_pos - 1); 1.850 + } 1.851 + rv = SetCurrentDictionary(lang); 1.852 + } 1.853 + if (NS_FAILED(rv)) { 1.854 + rv = SetCurrentDictionary(NS_LITERAL_STRING("en-US")); 1.855 + if (NS_FAILED(rv)) { 1.856 + nsTArray<nsString> dictList; 1.857 + rv = mSpellChecker->GetDictionaryList(&dictList); 1.858 + if (NS_SUCCEEDED(rv) && dictList.Length() > 0) { 1.859 + SetCurrentDictionary(dictList[0]); 1.860 + } 1.861 + } 1.862 + } 1.863 + } 1.864 + } 1.865 + 1.866 + // If an error was thrown while setting the dictionary, just 1.867 + // fail silently so that the spellchecker dialog is allowed to come 1.868 + // up. The user can manually reset the language to their choice on 1.869 + // the dialog if it is wrong. 1.870 + 1.871 + DeleteSuggestedWordList(); 1.872 + 1.873 + return NS_OK; 1.874 +}