editor/composer/src/nsEditorSpellCheck.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include <stdlib.h> // for getenv
michael@0 7
michael@0 8 #include "mozilla/Attributes.h" // for MOZ_FINAL
michael@0 9 #include "mozilla/Preferences.h" // for Preferences
michael@0 10 #include "mozilla/Services.h" // for GetXULChromeRegistryService
michael@0 11 #include "mozilla/dom/Element.h" // for Element
michael@0 12 #include "mozilla/mozalloc.h" // for operator delete, etc
michael@0 13 #include "nsAString.h" // for nsAString_internal::IsEmpty, etc
michael@0 14 #include "nsComponentManagerUtils.h" // for do_CreateInstance
michael@0 15 #include "nsDebug.h" // for NS_ENSURE_TRUE, etc
michael@0 16 #include "nsDependentSubstring.h" // for Substring
michael@0 17 #include "nsEditorSpellCheck.h"
michael@0 18 #include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc
michael@0 19 #include "nsIChromeRegistry.h" // for nsIXULChromeRegistry
michael@0 20 #include "nsIContent.h" // for nsIContent
michael@0 21 #include "nsIContentPrefService.h" // for nsIContentPrefService, etc
michael@0 22 #include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc
michael@0 23 #include "nsIDOMDocument.h" // for nsIDOMDocument
michael@0 24 #include "nsIDOMElement.h" // for nsIDOMElement
michael@0 25 #include "nsIDOMRange.h" // for nsIDOMRange
michael@0 26 #include "nsIDocument.h" // for nsIDocument
michael@0 27 #include "nsIEditor.h" // for nsIEditor
michael@0 28 #include "nsIHTMLEditor.h" // for nsIHTMLEditor
michael@0 29 #include "nsILoadContext.h"
michael@0 30 #include "nsISelection.h" // for nsISelection
michael@0 31 #include "nsISpellChecker.h" // for nsISpellChecker, etc
michael@0 32 #include "nsISupportsBase.h" // for nsISupports
michael@0 33 #include "nsISupportsUtils.h" // for NS_ADDREF
michael@0 34 #include "nsITextServicesDocument.h" // for nsITextServicesDocument
michael@0 35 #include "nsITextServicesFilter.h" // for nsITextServicesFilter
michael@0 36 #include "nsIURI.h" // for nsIURI
michael@0 37 #include "nsIVariant.h" // for nsIWritableVariant, etc
michael@0 38 #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc
michael@0 39 #include "nsMemory.h" // for nsMemory
michael@0 40 #include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc
michael@0 41 #include "nsServiceManagerUtils.h" // for do_GetService
michael@0 42 #include "nsString.h" // for nsAutoString, nsString, etc
michael@0 43 #include "nsStringFwd.h" // for nsAFlatString
michael@0 44 #include "nsStyleUtil.h" // for nsStyleUtil
michael@0 45
michael@0 46 using namespace mozilla;
michael@0 47
michael@0 48 class UpdateDictionnaryHolder {
michael@0 49 private:
michael@0 50 nsEditorSpellCheck* mSpellCheck;
michael@0 51 public:
michael@0 52 UpdateDictionnaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
michael@0 53 if (mSpellCheck) {
michael@0 54 mSpellCheck->BeginUpdateDictionary();
michael@0 55 }
michael@0 56 }
michael@0 57 ~UpdateDictionnaryHolder() {
michael@0 58 if (mSpellCheck) {
michael@0 59 mSpellCheck->EndUpdateDictionary();
michael@0 60 }
michael@0 61 }
michael@0 62 };
michael@0 63
michael@0 64 #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
michael@0 65
michael@0 66 /**
michael@0 67 * Gets the URI of aEditor's document.
michael@0 68 */
michael@0 69 static nsresult
michael@0 70 GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
michael@0 71 {
michael@0 72 NS_ENSURE_ARG_POINTER(aEditor);
michael@0 73 NS_ENSURE_ARG_POINTER(aURI);
michael@0 74
michael@0 75 nsCOMPtr<nsIDOMDocument> domDoc;
michael@0 76 aEditor->GetDocument(getter_AddRefs(domDoc));
michael@0 77 NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
michael@0 78
michael@0 79 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
michael@0 80 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
michael@0 81
michael@0 82 nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
michael@0 83 NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
michael@0 84
michael@0 85 *aURI = docUri;
michael@0 86 NS_ADDREF(*aURI);
michael@0 87 return NS_OK;
michael@0 88 }
michael@0 89
michael@0 90 static already_AddRefed<nsILoadContext>
michael@0 91 GetLoadContext(nsIEditor* aEditor)
michael@0 92 {
michael@0 93 nsCOMPtr<nsIDOMDocument> domDoc;
michael@0 94 aEditor->GetDocument(getter_AddRefs(domDoc));
michael@0 95 NS_ENSURE_TRUE(domDoc, nullptr);
michael@0 96
michael@0 97 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
michael@0 98 NS_ENSURE_TRUE(doc, nullptr);
michael@0 99
michael@0 100 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
michael@0 101 return loadContext.forget();
michael@0 102 }
michael@0 103
michael@0 104 /**
michael@0 105 * Fetches the dictionary stored in content prefs and maintains state during the
michael@0 106 * fetch, which is asynchronous.
michael@0 107 */
michael@0 108 class DictionaryFetcher MOZ_FINAL : public nsIContentPrefCallback2
michael@0 109 {
michael@0 110 public:
michael@0 111 NS_DECL_ISUPPORTS
michael@0 112
michael@0 113 DictionaryFetcher(nsEditorSpellCheck* aSpellCheck,
michael@0 114 nsIEditorSpellCheckCallback* aCallback,
michael@0 115 uint32_t aGroup)
michael@0 116 : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {}
michael@0 117
michael@0 118 NS_IMETHOD Fetch(nsIEditor* aEditor);
michael@0 119
michael@0 120 NS_IMETHOD HandleResult(nsIContentPref* aPref)
michael@0 121 {
michael@0 122 nsCOMPtr<nsIVariant> value;
michael@0 123 nsresult rv = aPref->GetValue(getter_AddRefs(value));
michael@0 124 NS_ENSURE_SUCCESS(rv, rv);
michael@0 125 value->GetAsAString(mDictionary);
michael@0 126 return NS_OK;
michael@0 127 }
michael@0 128
michael@0 129 NS_IMETHOD HandleCompletion(uint16_t reason)
michael@0 130 {
michael@0 131 mSpellCheck->DictionaryFetched(this);
michael@0 132 return NS_OK;
michael@0 133 }
michael@0 134
michael@0 135 NS_IMETHOD HandleError(nsresult error)
michael@0 136 {
michael@0 137 return NS_OK;
michael@0 138 }
michael@0 139
michael@0 140 nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
michael@0 141 uint32_t mGroup;
michael@0 142 nsString mRootContentLang;
michael@0 143 nsString mRootDocContentLang;
michael@0 144 nsString mDictionary;
michael@0 145
michael@0 146 private:
michael@0 147 nsRefPtr<nsEditorSpellCheck> mSpellCheck;
michael@0 148 };
michael@0 149 NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2)
michael@0 150
michael@0 151 NS_IMETHODIMP
michael@0 152 DictionaryFetcher::Fetch(nsIEditor* aEditor)
michael@0 153 {
michael@0 154 NS_ENSURE_ARG_POINTER(aEditor);
michael@0 155
michael@0 156 nsresult rv;
michael@0 157
michael@0 158 nsCOMPtr<nsIURI> docUri;
michael@0 159 rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
michael@0 160 NS_ENSURE_SUCCESS(rv, rv);
michael@0 161
michael@0 162 nsAutoCString docUriSpec;
michael@0 163 rv = docUri->GetSpec(docUriSpec);
michael@0 164 NS_ENSURE_SUCCESS(rv, rv);
michael@0 165
michael@0 166 nsCOMPtr<nsIContentPrefService2> contentPrefService =
michael@0 167 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
michael@0 168 NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
michael@0 169
michael@0 170 nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
michael@0 171 rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec),
michael@0 172 CPS_PREF_NAME, loadContext,
michael@0 173 this);
michael@0 174 NS_ENSURE_SUCCESS(rv, rv);
michael@0 175
michael@0 176 return NS_OK;
michael@0 177 }
michael@0 178
michael@0 179 /**
michael@0 180 * Stores the current dictionary for aEditor's document URL.
michael@0 181 */
michael@0 182 static nsresult
michael@0 183 StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
michael@0 184 {
michael@0 185 NS_ENSURE_ARG_POINTER(aEditor);
michael@0 186
michael@0 187 nsresult rv;
michael@0 188
michael@0 189 nsCOMPtr<nsIURI> docUri;
michael@0 190 rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
michael@0 191 NS_ENSURE_SUCCESS(rv, rv);
michael@0 192
michael@0 193 nsAutoCString docUriSpec;
michael@0 194 rv = docUri->GetSpec(docUriSpec);
michael@0 195 NS_ENSURE_SUCCESS(rv, rv);
michael@0 196
michael@0 197 nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
michael@0 198 NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
michael@0 199 prefValue->SetAsAString(aDictionary);
michael@0 200
michael@0 201 nsCOMPtr<nsIContentPrefService2> contentPrefService =
michael@0 202 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
michael@0 203 NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
michael@0 204
michael@0 205 nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
michael@0 206 return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
michael@0 207 CPS_PREF_NAME, prefValue, loadContext,
michael@0 208 nullptr);
michael@0 209 }
michael@0 210
michael@0 211 /**
michael@0 212 * Forgets the current dictionary stored for aEditor's document URL.
michael@0 213 */
michael@0 214 static nsresult
michael@0 215 ClearCurrentDictionary(nsIEditor* aEditor)
michael@0 216 {
michael@0 217 NS_ENSURE_ARG_POINTER(aEditor);
michael@0 218
michael@0 219 nsresult rv;
michael@0 220
michael@0 221 nsCOMPtr<nsIURI> docUri;
michael@0 222 rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
michael@0 223 NS_ENSURE_SUCCESS(rv, rv);
michael@0 224
michael@0 225 nsAutoCString docUriSpec;
michael@0 226 rv = docUri->GetSpec(docUriSpec);
michael@0 227 NS_ENSURE_SUCCESS(rv, rv);
michael@0 228
michael@0 229 nsCOMPtr<nsIContentPrefService2> contentPrefService =
michael@0 230 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
michael@0 231 NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
michael@0 232
michael@0 233 nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
michael@0 234 return contentPrefService->RemoveByDomainAndName(
michael@0 235 NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr);
michael@0 236 }
michael@0 237
michael@0 238 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
michael@0 239 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
michael@0 240
michael@0 241 NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
michael@0 242 NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
michael@0 243 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
michael@0 244 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
michael@0 245 NS_INTERFACE_MAP_END
michael@0 246
michael@0 247 NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck,
michael@0 248 mEditor,
michael@0 249 mSpellChecker,
michael@0 250 mTxtSrvFilter)
michael@0 251
michael@0 252 nsEditorSpellCheck::nsEditorSpellCheck()
michael@0 253 : mSuggestedWordIndex(0)
michael@0 254 , mDictionaryIndex(0)
michael@0 255 , mEditor(nullptr)
michael@0 256 , mDictionaryFetcherGroup(0)
michael@0 257 , mUpdateDictionaryRunning(false)
michael@0 258 {
michael@0 259 }
michael@0 260
michael@0 261 nsEditorSpellCheck::~nsEditorSpellCheck()
michael@0 262 {
michael@0 263 // Make sure we blow the spellchecker away, just in
michael@0 264 // case it hasn't been destroyed already.
michael@0 265 mSpellChecker = nullptr;
michael@0 266 }
michael@0 267
michael@0 268 // The problem is that if the spell checker does not exist, we can not tell
michael@0 269 // which dictionaries are installed. This function works around the problem,
michael@0 270 // allowing callers to ask if we can spell check without actually doing so (and
michael@0 271 // enabling or disabling UI as necessary). This just creates a spellcheck
michael@0 272 // object if needed and asks it for the dictionary list.
michael@0 273 NS_IMETHODIMP
michael@0 274 nsEditorSpellCheck::CanSpellCheck(bool* _retval)
michael@0 275 {
michael@0 276 nsresult rv;
michael@0 277 nsCOMPtr<nsISpellChecker> spellChecker;
michael@0 278 if (! mSpellChecker) {
michael@0 279 spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
michael@0 280 NS_ENSURE_SUCCESS(rv, rv);
michael@0 281 } else {
michael@0 282 spellChecker = mSpellChecker;
michael@0 283 }
michael@0 284 nsTArray<nsString> dictList;
michael@0 285 rv = spellChecker->GetDictionaryList(&dictList);
michael@0 286 NS_ENSURE_SUCCESS(rv, rv);
michael@0 287
michael@0 288 *_retval = (dictList.Length() > 0);
michael@0 289 return NS_OK;
michael@0 290 }
michael@0 291
michael@0 292 // Instances of this class can be used as either runnables or RAII helpers.
michael@0 293 class CallbackCaller MOZ_FINAL : public nsRunnable
michael@0 294 {
michael@0 295 public:
michael@0 296 explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
michael@0 297 : mCallback(aCallback) {}
michael@0 298
michael@0 299 ~CallbackCaller()
michael@0 300 {
michael@0 301 Run();
michael@0 302 }
michael@0 303
michael@0 304 NS_IMETHOD Run()
michael@0 305 {
michael@0 306 if (mCallback) {
michael@0 307 mCallback->EditorSpellCheckDone();
michael@0 308 mCallback = nullptr;
michael@0 309 }
michael@0 310 return NS_OK;
michael@0 311 }
michael@0 312
michael@0 313 private:
michael@0 314 nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
michael@0 315 };
michael@0 316
michael@0 317 NS_IMETHODIMP
michael@0 318 nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback)
michael@0 319 {
michael@0 320 NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
michael@0 321 mEditor = aEditor;
michael@0 322
michael@0 323 nsresult rv;
michael@0 324
michael@0 325 // We can spell check with any editor type
michael@0 326 nsCOMPtr<nsITextServicesDocument>tsDoc =
michael@0 327 do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
michael@0 328 NS_ENSURE_SUCCESS(rv, rv);
michael@0 329
michael@0 330 NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
michael@0 331
michael@0 332 tsDoc->SetFilter(mTxtSrvFilter);
michael@0 333
michael@0 334 // Pass the editor to the text services document
michael@0 335 rv = tsDoc->InitWithEditor(aEditor);
michael@0 336 NS_ENSURE_SUCCESS(rv, rv);
michael@0 337
michael@0 338 if (aEnableSelectionChecking) {
michael@0 339 // Find out if the section is collapsed or not.
michael@0 340 // If it isn't, we want to spellcheck just the selection.
michael@0 341
michael@0 342 nsCOMPtr<nsISelection> selection;
michael@0 343
michael@0 344 rv = aEditor->GetSelection(getter_AddRefs(selection));
michael@0 345 NS_ENSURE_SUCCESS(rv, rv);
michael@0 346 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
michael@0 347
michael@0 348 int32_t count = 0;
michael@0 349
michael@0 350 rv = selection->GetRangeCount(&count);
michael@0 351 NS_ENSURE_SUCCESS(rv, rv);
michael@0 352
michael@0 353 if (count > 0) {
michael@0 354 nsCOMPtr<nsIDOMRange> range;
michael@0 355
michael@0 356 rv = selection->GetRangeAt(0, getter_AddRefs(range));
michael@0 357 NS_ENSURE_SUCCESS(rv, rv);
michael@0 358
michael@0 359 bool collapsed = false;
michael@0 360 rv = range->GetCollapsed(&collapsed);
michael@0 361 NS_ENSURE_SUCCESS(rv, rv);
michael@0 362
michael@0 363 if (!collapsed) {
michael@0 364 // We don't want to touch the range in the selection,
michael@0 365 // so create a new copy of it.
michael@0 366
michael@0 367 nsCOMPtr<nsIDOMRange> rangeBounds;
michael@0 368 rv = range->CloneRange(getter_AddRefs(rangeBounds));
michael@0 369 NS_ENSURE_SUCCESS(rv, rv);
michael@0 370 NS_ENSURE_TRUE(rangeBounds, NS_ERROR_FAILURE);
michael@0 371
michael@0 372 // Make sure the new range spans complete words.
michael@0 373
michael@0 374 rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds);
michael@0 375 NS_ENSURE_SUCCESS(rv, rv);
michael@0 376
michael@0 377 // Now tell the text services that you only want
michael@0 378 // to iterate over the text in this range.
michael@0 379
michael@0 380 rv = tsDoc->SetExtent(rangeBounds);
michael@0 381 NS_ENSURE_SUCCESS(rv, rv);
michael@0 382 }
michael@0 383 }
michael@0 384 }
michael@0 385
michael@0 386 mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
michael@0 387 NS_ENSURE_SUCCESS(rv, rv);
michael@0 388
michael@0 389 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
michael@0 390
michael@0 391 rv = mSpellChecker->SetDocument(tsDoc, true);
michael@0 392 NS_ENSURE_SUCCESS(rv, rv);
michael@0 393
michael@0 394 // do not fail if UpdateCurrentDictionary fails because this method may
michael@0 395 // succeed later.
michael@0 396 rv = UpdateCurrentDictionary(aCallback);
michael@0 397 if (NS_FAILED(rv) && aCallback) {
michael@0 398 // However, if it does fail, we still need to call the callback since we
michael@0 399 // discard the failure. Do it asynchronously so that the caller is always
michael@0 400 // guaranteed async behavior.
michael@0 401 nsRefPtr<CallbackCaller> caller = new CallbackCaller(aCallback);
michael@0 402 NS_ENSURE_STATE(caller);
michael@0 403 rv = NS_DispatchToMainThread(caller);
michael@0 404 NS_ENSURE_SUCCESS(rv, rv);
michael@0 405 }
michael@0 406
michael@0 407 return NS_OK;
michael@0 408 }
michael@0 409
michael@0 410 NS_IMETHODIMP
michael@0 411 nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord)
michael@0 412 {
michael@0 413 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 414
michael@0 415 nsAutoString nextMisspelledWord;
michael@0 416
michael@0 417 DeleteSuggestedWordList();
michael@0 418 // Beware! This may flush notifications via synchronous
michael@0 419 // ScrollSelectionIntoView.
michael@0 420 nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord,
michael@0 421 &mSuggestedWordList);
michael@0 422
michael@0 423 *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord);
michael@0 424 return rv;
michael@0 425 }
michael@0 426
michael@0 427 NS_IMETHODIMP
michael@0 428 nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord)
michael@0 429 {
michael@0 430 nsAutoString word;
michael@0 431 if ( mSuggestedWordIndex < int32_t(mSuggestedWordList.Length()))
michael@0 432 {
michael@0 433 *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]);
michael@0 434 mSuggestedWordIndex++;
michael@0 435 } else {
michael@0 436 // A blank string signals that there are no more strings
michael@0 437 *aSuggestedWord = ToNewUnicode(EmptyString());
michael@0 438 }
michael@0 439 return NS_OK;
michael@0 440 }
michael@0 441
michael@0 442 NS_IMETHODIMP
michael@0 443 nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord,
michael@0 444 bool *aIsMisspelled)
michael@0 445 {
michael@0 446 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 447
michael@0 448 DeleteSuggestedWordList();
michael@0 449 return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
michael@0 450 aIsMisspelled, &mSuggestedWordList);
michael@0 451 }
michael@0 452
michael@0 453 NS_IMETHODIMP
michael@0 454 nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord,
michael@0 455 bool *aIsMisspelled)
michael@0 456 {
michael@0 457 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 458
michael@0 459 return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
michael@0 460 aIsMisspelled, nullptr);
michael@0 461 }
michael@0 462
michael@0 463 NS_IMETHODIMP
michael@0 464 nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord,
michael@0 465 const char16_t *aReplaceWord,
michael@0 466 bool allOccurrences)
michael@0 467 {
michael@0 468 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 469
michael@0 470 return mSpellChecker->Replace(nsDependentString(aMisspelledWord),
michael@0 471 nsDependentString(aReplaceWord), allOccurrences);
michael@0 472 }
michael@0 473
michael@0 474 NS_IMETHODIMP
michael@0 475 nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord)
michael@0 476 {
michael@0 477 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 478
michael@0 479 return mSpellChecker->IgnoreAll(nsDependentString(aWord));
michael@0 480 }
michael@0 481
michael@0 482 NS_IMETHODIMP
michael@0 483 nsEditorSpellCheck::GetPersonalDictionary()
michael@0 484 {
michael@0 485 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 486
michael@0 487 // We can spell check with any editor type
michael@0 488 mDictionaryList.Clear();
michael@0 489 mDictionaryIndex = 0;
michael@0 490 return mSpellChecker->GetPersonalDictionary(&mDictionaryList);
michael@0 491 }
michael@0 492
michael@0 493 NS_IMETHODIMP
michael@0 494 nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord)
michael@0 495 {
michael@0 496 if ( mDictionaryIndex < int32_t( mDictionaryList.Length()))
michael@0 497 {
michael@0 498 *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]);
michael@0 499 mDictionaryIndex++;
michael@0 500 } else {
michael@0 501 // A blank string signals that there are no more strings
michael@0 502 *aDictionaryWord = ToNewUnicode(EmptyString());
michael@0 503 }
michael@0 504
michael@0 505 return NS_OK;
michael@0 506 }
michael@0 507
michael@0 508 NS_IMETHODIMP
michael@0 509 nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord)
michael@0 510 {
michael@0 511 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 512
michael@0 513 return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord));
michael@0 514 }
michael@0 515
michael@0 516 NS_IMETHODIMP
michael@0 517 nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord)
michael@0 518 {
michael@0 519 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 520
michael@0 521 return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord));
michael@0 522 }
michael@0 523
michael@0 524 NS_IMETHODIMP
michael@0 525 nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount)
michael@0 526 {
michael@0 527 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 528
michael@0 529 NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER);
michael@0 530
michael@0 531 *aDictionaryList = 0;
michael@0 532 *aCount = 0;
michael@0 533
michael@0 534 nsTArray<nsString> dictList;
michael@0 535
michael@0 536 nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
michael@0 537
michael@0 538 NS_ENSURE_SUCCESS(rv, rv);
michael@0 539
michael@0 540 char16_t **tmpPtr = 0;
michael@0 541
michael@0 542 if (dictList.Length() < 1)
michael@0 543 {
michael@0 544 // If there are no dictionaries, return an array containing
michael@0 545 // one element and a count of one.
michael@0 546
michael@0 547 tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *));
michael@0 548
michael@0 549 NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
michael@0 550
michael@0 551 *tmpPtr = 0;
michael@0 552 *aDictionaryList = tmpPtr;
michael@0 553 *aCount = 0;
michael@0 554
michael@0 555 return NS_OK;
michael@0 556 }
michael@0 557
michael@0 558 tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *) * dictList.Length());
michael@0 559
michael@0 560 NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
michael@0 561
michael@0 562 *aDictionaryList = tmpPtr;
michael@0 563 *aCount = dictList.Length();
michael@0 564
michael@0 565 uint32_t i;
michael@0 566
michael@0 567 for (i = 0; i < *aCount; i++)
michael@0 568 {
michael@0 569 tmpPtr[i] = ToNewUnicode(dictList[i]);
michael@0 570 }
michael@0 571
michael@0 572 return rv;
michael@0 573 }
michael@0 574
michael@0 575 NS_IMETHODIMP
michael@0 576 nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary)
michael@0 577 {
michael@0 578 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 579
michael@0 580 return mSpellChecker->GetCurrentDictionary(aDictionary);
michael@0 581 }
michael@0 582
michael@0 583 NS_IMETHODIMP
michael@0 584 nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
michael@0 585 {
michael@0 586 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 587
michael@0 588 nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
michael@0 589
michael@0 590 // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if
michael@0 591 // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us,
michael@0 592 // is on the stack.
michael@0 593 if (!mUpdateDictionaryRunning) {
michael@0 594
michael@0 595 // Ignore pending dictionary fetchers by increasing this number.
michael@0 596 mDictionaryFetcherGroup++;
michael@0 597
michael@0 598 nsDefaultStringComparator comparator;
michael@0 599 nsAutoString langCode;
michael@0 600 int32_t dashIdx = aDictionary.FindChar('-');
michael@0 601 if (dashIdx != -1) {
michael@0 602 langCode.Assign(Substring(aDictionary, 0, dashIdx));
michael@0 603 } else {
michael@0 604 langCode.Assign(aDictionary);
michael@0 605 }
michael@0 606
michael@0 607 if (mPreferredLang.IsEmpty() || !nsStyleUtil::DashMatchCompare(mPreferredLang, langCode, comparator)) {
michael@0 608 // When user sets dictionary manually, we store this value associated
michael@0 609 // with editor url.
michael@0 610 StoreCurrentDictionary(mEditor, aDictionary);
michael@0 611 } else {
michael@0 612 // If user sets a dictionary matching (even partially), lang defined by
michael@0 613 // document, we consider content pref has been canceled, and we clear it.
michael@0 614 ClearCurrentDictionary(mEditor);
michael@0 615 }
michael@0 616
michael@0 617 // Also store it in as a preference. It will be used as a default value
michael@0 618 // when everything else fails.
michael@0 619 Preferences::SetString("spellchecker.dictionary", aDictionary);
michael@0 620 }
michael@0 621 return mSpellChecker->SetCurrentDictionary(aDictionary);
michael@0 622 }
michael@0 623
michael@0 624 NS_IMETHODIMP
michael@0 625 nsEditorSpellCheck::CheckCurrentDictionary()
michael@0 626 {
michael@0 627 mSpellChecker->CheckCurrentDictionary();
michael@0 628
michael@0 629 // Check if our current dictionary is still available.
michael@0 630 nsAutoString currentDictionary;
michael@0 631 nsresult rv = GetCurrentDictionary(currentDictionary);
michael@0 632 if (NS_SUCCEEDED(rv) && !currentDictionary.IsEmpty()) {
michael@0 633 return NS_OK;
michael@0 634 }
michael@0 635
michael@0 636 // If our preferred current dictionary has gone, pick another one.
michael@0 637 nsTArray<nsString> dictList;
michael@0 638 rv = mSpellChecker->GetDictionaryList(&dictList);
michael@0 639 NS_ENSURE_SUCCESS(rv, rv);
michael@0 640
michael@0 641 if (dictList.Length() > 0) {
michael@0 642 rv = SetCurrentDictionary(dictList[0]);
michael@0 643 NS_ENSURE_SUCCESS(rv, rv);
michael@0 644 }
michael@0 645
michael@0 646 return NS_OK;
michael@0 647 }
michael@0 648
michael@0 649 NS_IMETHODIMP
michael@0 650 nsEditorSpellCheck::UninitSpellChecker()
michael@0 651 {
michael@0 652 NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
michael@0 653
michael@0 654 // Cleanup - kill the spell checker
michael@0 655 DeleteSuggestedWordList();
michael@0 656 mDictionaryList.Clear();
michael@0 657 mDictionaryIndex = 0;
michael@0 658 mSpellChecker = 0;
michael@0 659 return NS_OK;
michael@0 660 }
michael@0 661
michael@0 662
michael@0 663 /* void setFilter (in nsITextServicesFilter filter); */
michael@0 664 NS_IMETHODIMP
michael@0 665 nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter)
michael@0 666 {
michael@0 667 mTxtSrvFilter = filter;
michael@0 668 return NS_OK;
michael@0 669 }
michael@0 670
michael@0 671 nsresult
michael@0 672 nsEditorSpellCheck::DeleteSuggestedWordList()
michael@0 673 {
michael@0 674 mSuggestedWordList.Clear();
michael@0 675 mSuggestedWordIndex = 0;
michael@0 676 return NS_OK;
michael@0 677 }
michael@0 678
michael@0 679 NS_IMETHODIMP
michael@0 680 nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback)
michael@0 681 {
michael@0 682 nsresult rv;
michael@0 683
michael@0 684 nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
michael@0 685
michael@0 686 // Get language with html5 algorithm
michael@0 687 nsCOMPtr<nsIContent> rootContent;
michael@0 688 nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
michael@0 689 if (htmlEditor) {
michael@0 690 rootContent = htmlEditor->GetActiveEditingHost();
michael@0 691 } else {
michael@0 692 nsCOMPtr<nsIDOMElement> rootElement;
michael@0 693 rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
michael@0 694 NS_ENSURE_SUCCESS(rv, rv);
michael@0 695 rootContent = do_QueryInterface(rootElement);
michael@0 696 }
michael@0 697 NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
michael@0 698
michael@0 699 DictionaryFetcher* fetcher = new DictionaryFetcher(this, aCallback,
michael@0 700 mDictionaryFetcherGroup);
michael@0 701 rootContent->GetLang(fetcher->mRootContentLang);
michael@0 702 nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
michael@0 703 NS_ENSURE_STATE(doc);
michael@0 704 doc->GetContentLanguage(fetcher->mRootDocContentLang);
michael@0 705
michael@0 706 rv = fetcher->Fetch(mEditor);
michael@0 707 NS_ENSURE_SUCCESS(rv, rv);
michael@0 708
michael@0 709 return NS_OK;
michael@0 710 }
michael@0 711
michael@0 712 nsresult
michael@0 713 nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
michael@0 714 {
michael@0 715 nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
michael@0 716
michael@0 717 nsresult rv = NS_OK;
michael@0 718
michael@0 719 // Important: declare the holder after the callback caller so that the former
michael@0 720 // is destructed first so that it's not active when the callback is called.
michael@0 721 CallbackCaller callbackCaller(aFetcher->mCallback);
michael@0 722 UpdateDictionnaryHolder holder(this);
michael@0 723
michael@0 724 if (aFetcher->mGroup < mDictionaryFetcherGroup) {
michael@0 725 // SetCurrentDictionary was called after the fetch started. Don't overwrite
michael@0 726 // that dictionary with the fetched one.
michael@0 727 return NS_OK;
michael@0 728 }
michael@0 729
michael@0 730 mPreferredLang.Assign(aFetcher->mRootContentLang);
michael@0 731
michael@0 732 // If we successfully fetched a dictionary from content prefs, do not go
michael@0 733 // further. Use this exact dictionary.
michael@0 734 nsAutoString dictName;
michael@0 735 dictName.Assign(aFetcher->mDictionary);
michael@0 736 if (!dictName.IsEmpty()) {
michael@0 737 if (NS_FAILED(SetCurrentDictionary(dictName))) {
michael@0 738 // may be dictionary was uninstalled ?
michael@0 739 ClearCurrentDictionary(mEditor);
michael@0 740 }
michael@0 741 return NS_OK;
michael@0 742 }
michael@0 743
michael@0 744 if (mPreferredLang.IsEmpty()) {
michael@0 745 mPreferredLang.Assign(aFetcher->mRootDocContentLang);
michael@0 746 }
michael@0 747
michael@0 748 // Then, try to use language computed from element
michael@0 749 if (!mPreferredLang.IsEmpty()) {
michael@0 750 dictName.Assign(mPreferredLang);
michael@0 751 }
michael@0 752
michael@0 753 // otherwise, get language from preferences
michael@0 754 nsAutoString preferedDict(Preferences::GetLocalizedString("spellchecker.dictionary"));
michael@0 755 if (dictName.IsEmpty()) {
michael@0 756 dictName.Assign(preferedDict);
michael@0 757 }
michael@0 758
michael@0 759 if (dictName.IsEmpty())
michael@0 760 {
michael@0 761 // Prefs didn't give us a dictionary name, so just get the current
michael@0 762 // locale and use that as the default dictionary name!
michael@0 763
michael@0 764 nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
michael@0 765 mozilla::services::GetXULChromeRegistryService();
michael@0 766
michael@0 767 if (packageRegistry) {
michael@0 768 nsAutoCString utf8DictName;
michael@0 769 rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"),
michael@0 770 utf8DictName);
michael@0 771 AppendUTF8toUTF16(utf8DictName, dictName);
michael@0 772 }
michael@0 773 }
michael@0 774
michael@0 775 if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
michael@0 776 rv = SetCurrentDictionary(dictName);
michael@0 777 if (NS_FAILED(rv)) {
michael@0 778 // required dictionary was not available. Try to get a dictionary
michael@0 779 // matching at least language part of dictName:
michael@0 780
michael@0 781 nsAutoString langCode;
michael@0 782 int32_t dashIdx = dictName.FindChar('-');
michael@0 783 if (dashIdx != -1) {
michael@0 784 langCode.Assign(Substring(dictName, 0, dashIdx));
michael@0 785 } else {
michael@0 786 langCode.Assign(dictName);
michael@0 787 }
michael@0 788
michael@0 789 nsDefaultStringComparator comparator;
michael@0 790
michael@0 791 // try dictionary.spellchecker preference if it starts with langCode (and
michael@0 792 // if we haven't tried it already)
michael@0 793 if (!preferedDict.IsEmpty() && !dictName.Equals(preferedDict) &&
michael@0 794 nsStyleUtil::DashMatchCompare(preferedDict, langCode, comparator)) {
michael@0 795 rv = SetCurrentDictionary(preferedDict);
michael@0 796 }
michael@0 797
michael@0 798 // Otherwise, try langCode (if we haven't tried it already)
michael@0 799 if (NS_FAILED(rv)) {
michael@0 800 if (!dictName.Equals(langCode) && !preferedDict.Equals(langCode)) {
michael@0 801 rv = SetCurrentDictionary(langCode);
michael@0 802 }
michael@0 803 }
michael@0 804
michael@0 805 // Otherwise, try any available dictionary aa-XX
michael@0 806 if (NS_FAILED(rv)) {
michael@0 807 // loop over avaible dictionaries; if we find one with required
michael@0 808 // language, use it
michael@0 809 nsTArray<nsString> dictList;
michael@0 810 rv = mSpellChecker->GetDictionaryList(&dictList);
michael@0 811 NS_ENSURE_SUCCESS(rv, rv);
michael@0 812 int32_t i, count = dictList.Length();
michael@0 813 for (i = 0; i < count; i++) {
michael@0 814 nsAutoString dictStr(dictList.ElementAt(i));
michael@0 815
michael@0 816 if (dictStr.Equals(dictName) ||
michael@0 817 dictStr.Equals(preferedDict) ||
michael@0 818 dictStr.Equals(langCode)) {
michael@0 819 // We have already tried it
michael@0 820 continue;
michael@0 821 }
michael@0 822
michael@0 823 if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) &&
michael@0 824 NS_SUCCEEDED(SetCurrentDictionary(dictStr))) {
michael@0 825 break;
michael@0 826 }
michael@0 827 }
michael@0 828 }
michael@0 829 }
michael@0 830 }
michael@0 831
michael@0 832 // If we have not set dictionary, and the editable element doesn't have a
michael@0 833 // lang attribute, we try to get a dictionary. First try LANG environment variable,
michael@0 834 // then en-US. If it does not work, pick the first one.
michael@0 835 if (mPreferredLang.IsEmpty()) {
michael@0 836 nsAutoString currentDictionary;
michael@0 837 rv = GetCurrentDictionary(currentDictionary);
michael@0 838 if (NS_FAILED(rv) || currentDictionary.IsEmpty()) {
michael@0 839 // Try to get current dictionary from environment variable LANG
michael@0 840 char* env_lang = getenv("LANG");
michael@0 841 if (env_lang != nullptr) {
michael@0 842 nsString lang = NS_ConvertUTF8toUTF16(env_lang);
michael@0 843 // Strip trailing charset if there is any
michael@0 844 int32_t dot_pos = lang.FindChar('.');
michael@0 845 if (dot_pos != -1) {
michael@0 846 lang = Substring(lang, 0, dot_pos - 1);
michael@0 847 }
michael@0 848 rv = SetCurrentDictionary(lang);
michael@0 849 }
michael@0 850 if (NS_FAILED(rv)) {
michael@0 851 rv = SetCurrentDictionary(NS_LITERAL_STRING("en-US"));
michael@0 852 if (NS_FAILED(rv)) {
michael@0 853 nsTArray<nsString> dictList;
michael@0 854 rv = mSpellChecker->GetDictionaryList(&dictList);
michael@0 855 if (NS_SUCCEEDED(rv) && dictList.Length() > 0) {
michael@0 856 SetCurrentDictionary(dictList[0]);
michael@0 857 }
michael@0 858 }
michael@0 859 }
michael@0 860 }
michael@0 861 }
michael@0 862
michael@0 863 // If an error was thrown while setting the dictionary, just
michael@0 864 // fail silently so that the spellchecker dialog is allowed to come
michael@0 865 // up. The user can manually reset the language to their choice on
michael@0 866 // the dialog if it is wrong.
michael@0 867
michael@0 868 DeleteSuggestedWordList();
michael@0 869
michael@0 870 return NS_OK;
michael@0 871 }

mercurial