Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 sts=2 sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include <stdlib.h> // for getenv
8 :
9 : #include "mozilla/Attributes.h" // for final
10 : #include "mozilla/Preferences.h" // for Preferences
11 : #include "mozilla/dom/Element.h" // for Element
12 : #include "mozilla/dom/Selection.h"
13 : #include "mozilla/intl/LocaleService.h" // for retrieving app locale
14 : #include "mozilla/mozalloc.h" // for operator delete, etc
15 : #include "nsAString.h" // for nsAString::IsEmpty, etc
16 : #include "nsComponentManagerUtils.h" // for do_CreateInstance
17 : #include "nsDebug.h" // for NS_ENSURE_TRUE, etc
18 : #include "nsDependentSubstring.h" // for Substring
19 : #include "nsEditorSpellCheck.h"
20 : #include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc
21 : #include "nsIContent.h" // for nsIContent
22 : #include "nsIContentPrefService.h" // for nsIContentPrefService, etc
23 : #include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc
24 : #include "nsIDOMDocument.h" // for nsIDOMDocument
25 : #include "nsIDOMElement.h" // for nsIDOMElement
26 : #include "nsIDocument.h" // for nsIDocument
27 : #include "nsIEditor.h" // for nsIEditor
28 : #include "nsIHTMLEditor.h" // for nsIHTMLEditor
29 : #include "nsILoadContext.h"
30 : #include "nsISelection.h" // for nsISelection
31 : #include "nsISpellChecker.h" // for nsISpellChecker, etc
32 : #include "nsISupportsBase.h" // for nsISupports
33 : #include "nsISupportsUtils.h" // for NS_ADDREF
34 : #include "nsITextServicesDocument.h" // for nsITextServicesDocument
35 : #include "nsITextServicesFilter.h" // for nsITextServicesFilter
36 : #include "nsIURI.h" // for nsIURI
37 : #include "nsThreadUtils.h" // for GetMainThreadSerialEventTarget
38 : #include "nsVariant.h" // for nsIWritableVariant, etc
39 : #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc
40 : #include "nsMemory.h" // for nsMemory
41 : #include "nsRange.h"
42 : #include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc
43 : #include "nsServiceManagerUtils.h" // for do_GetService
44 : #include "nsString.h" // for nsAutoString, nsString, etc
45 : #include "nsStringFwd.h" // for nsAFlatString
46 : #include "nsStyleUtil.h" // for nsStyleUtil
47 : #include "nsXULAppAPI.h" // for XRE_GetProcessType
48 : #include "nsIPlaintextEditor.h" // for editor flags
49 :
50 : using namespace mozilla;
51 : using namespace mozilla::dom;
52 : using mozilla::intl::LocaleService;
53 :
54 : class UpdateDictionaryHolder {
55 : private:
56 : nsEditorSpellCheck* mSpellCheck;
57 : public:
58 : explicit UpdateDictionaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
59 : if (mSpellCheck) {
60 : mSpellCheck->BeginUpdateDictionary();
61 : }
62 : }
63 : ~UpdateDictionaryHolder() {
64 : if (mSpellCheck) {
65 : mSpellCheck->EndUpdateDictionary();
66 : }
67 : }
68 : };
69 :
70 : #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
71 :
72 : /**
73 : * Gets the URI of aEditor's document.
74 : */
75 : static nsresult
76 0 : GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
77 : {
78 0 : NS_ENSURE_ARG_POINTER(aEditor);
79 0 : NS_ENSURE_ARG_POINTER(aURI);
80 :
81 0 : nsCOMPtr<nsIDOMDocument> domDoc;
82 0 : aEditor->GetDocument(getter_AddRefs(domDoc));
83 0 : NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
84 :
85 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
86 0 : NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
87 :
88 0 : nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
89 0 : NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
90 :
91 0 : *aURI = docUri;
92 0 : NS_ADDREF(*aURI);
93 0 : return NS_OK;
94 : }
95 :
96 : static already_AddRefed<nsILoadContext>
97 0 : GetLoadContext(nsIEditor* aEditor)
98 : {
99 0 : nsCOMPtr<nsIDOMDocument> domDoc;
100 0 : aEditor->GetDocument(getter_AddRefs(domDoc));
101 0 : NS_ENSURE_TRUE(domDoc, nullptr);
102 :
103 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
104 0 : NS_ENSURE_TRUE(doc, nullptr);
105 :
106 0 : nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
107 0 : return loadContext.forget();
108 : }
109 :
110 : /**
111 : * Fetches the dictionary stored in content prefs and maintains state during the
112 : * fetch, which is asynchronous.
113 : */
114 : class DictionaryFetcher final : public nsIContentPrefCallback2
115 : {
116 : public:
117 : NS_DECL_ISUPPORTS
118 :
119 0 : DictionaryFetcher(nsEditorSpellCheck* aSpellCheck,
120 : nsIEditorSpellCheckCallback* aCallback,
121 : uint32_t aGroup)
122 0 : : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {}
123 :
124 : NS_IMETHOD Fetch(nsIEditor* aEditor);
125 :
126 0 : NS_IMETHOD HandleResult(nsIContentPref* aPref) override
127 : {
128 0 : nsCOMPtr<nsIVariant> value;
129 0 : nsresult rv = aPref->GetValue(getter_AddRefs(value));
130 0 : NS_ENSURE_SUCCESS(rv, rv);
131 0 : value->GetAsAString(mDictionary);
132 0 : return NS_OK;
133 : }
134 :
135 0 : NS_IMETHOD HandleCompletion(uint16_t reason) override
136 : {
137 0 : mSpellCheck->DictionaryFetched(this);
138 0 : return NS_OK;
139 : }
140 :
141 0 : NS_IMETHOD HandleError(nsresult error) override
142 : {
143 0 : return NS_OK;
144 : }
145 :
146 : nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
147 : uint32_t mGroup;
148 : nsString mRootContentLang;
149 : nsString mRootDocContentLang;
150 : nsString mDictionary;
151 :
152 : private:
153 0 : ~DictionaryFetcher() {}
154 :
155 : RefPtr<nsEditorSpellCheck> mSpellCheck;
156 : };
157 0 : NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2)
158 :
159 : NS_IMETHODIMP
160 0 : DictionaryFetcher::Fetch(nsIEditor* aEditor)
161 : {
162 0 : NS_ENSURE_ARG_POINTER(aEditor);
163 :
164 : nsresult rv;
165 :
166 0 : nsCOMPtr<nsIURI> docUri;
167 0 : rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
168 0 : NS_ENSURE_SUCCESS(rv, rv);
169 :
170 0 : nsAutoCString docUriSpec;
171 0 : rv = docUri->GetSpec(docUriSpec);
172 0 : NS_ENSURE_SUCCESS(rv, rv);
173 :
174 : nsCOMPtr<nsIContentPrefService2> contentPrefService =
175 0 : do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
176 0 : NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
177 :
178 0 : nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
179 0 : rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec),
180 0 : CPS_PREF_NAME, loadContext,
181 0 : this);
182 0 : NS_ENSURE_SUCCESS(rv, rv);
183 :
184 0 : return NS_OK;
185 : }
186 :
187 : /**
188 : * Stores the current dictionary for aEditor's document URL.
189 : */
190 : static nsresult
191 0 : StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
192 : {
193 0 : NS_ENSURE_ARG_POINTER(aEditor);
194 :
195 : nsresult rv;
196 :
197 0 : nsCOMPtr<nsIURI> docUri;
198 0 : rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
199 0 : NS_ENSURE_SUCCESS(rv, rv);
200 :
201 0 : nsAutoCString docUriSpec;
202 0 : rv = docUri->GetSpec(docUriSpec);
203 0 : NS_ENSURE_SUCCESS(rv, rv);
204 :
205 0 : RefPtr<nsVariant> prefValue = new nsVariant();
206 0 : prefValue->SetAsAString(aDictionary);
207 :
208 : nsCOMPtr<nsIContentPrefService2> contentPrefService =
209 0 : do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
210 0 : NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
211 :
212 0 : nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
213 0 : return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
214 0 : CPS_PREF_NAME, prefValue, loadContext,
215 0 : nullptr);
216 : }
217 :
218 : /**
219 : * Forgets the current dictionary stored for aEditor's document URL.
220 : */
221 : static nsresult
222 0 : ClearCurrentDictionary(nsIEditor* aEditor)
223 : {
224 0 : NS_ENSURE_ARG_POINTER(aEditor);
225 :
226 : nsresult rv;
227 :
228 0 : nsCOMPtr<nsIURI> docUri;
229 0 : rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
230 0 : NS_ENSURE_SUCCESS(rv, rv);
231 :
232 0 : nsAutoCString docUriSpec;
233 0 : rv = docUri->GetSpec(docUriSpec);
234 0 : NS_ENSURE_SUCCESS(rv, rv);
235 :
236 : nsCOMPtr<nsIContentPrefService2> contentPrefService =
237 0 : do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
238 0 : NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
239 :
240 0 : nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
241 0 : return contentPrefService->RemoveByDomainAndName(
242 0 : NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr);
243 : }
244 :
245 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
246 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
247 :
248 0 : NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
249 0 : NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
250 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
251 0 : NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
252 0 : NS_INTERFACE_MAP_END
253 :
254 0 : NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck,
255 : mEditor,
256 : mSpellChecker,
257 : mTxtSrvFilter)
258 :
259 0 : nsEditorSpellCheck::nsEditorSpellCheck()
260 : : mSuggestedWordIndex(0)
261 : , mDictionaryIndex(0)
262 : , mEditor(nullptr)
263 : , mDictionaryFetcherGroup(0)
264 0 : , mUpdateDictionaryRunning(false)
265 : {
266 0 : }
267 :
268 0 : nsEditorSpellCheck::~nsEditorSpellCheck()
269 : {
270 : // Make sure we blow the spellchecker away, just in
271 : // case it hasn't been destroyed already.
272 0 : mSpellChecker = nullptr;
273 0 : }
274 :
275 : // The problem is that if the spell checker does not exist, we can not tell
276 : // which dictionaries are installed. This function works around the problem,
277 : // allowing callers to ask if we can spell check without actually doing so (and
278 : // enabling or disabling UI as necessary). This just creates a spellcheck
279 : // object if needed and asks it for the dictionary list.
280 : NS_IMETHODIMP
281 0 : nsEditorSpellCheck::CanSpellCheck(bool* _retval)
282 : {
283 : nsresult rv;
284 0 : nsCOMPtr<nsISpellChecker> spellChecker;
285 0 : if (! mSpellChecker) {
286 0 : spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
287 0 : NS_ENSURE_SUCCESS(rv, rv);
288 : } else {
289 0 : spellChecker = mSpellChecker;
290 : }
291 0 : nsTArray<nsString> dictList;
292 0 : rv = spellChecker->GetDictionaryList(&dictList);
293 0 : NS_ENSURE_SUCCESS(rv, rv);
294 :
295 0 : *_retval = (dictList.Length() > 0);
296 0 : return NS_OK;
297 : }
298 :
299 : // Instances of this class can be used as either runnables or RAII helpers.
300 : class CallbackCaller final : public Runnable
301 : {
302 : public:
303 0 : explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
304 0 : : mozilla::Runnable("CallbackCaller")
305 0 : , mCallback(aCallback)
306 : {
307 0 : }
308 :
309 0 : ~CallbackCaller()
310 0 : {
311 0 : Run();
312 0 : }
313 :
314 0 : NS_IMETHOD Run() override
315 : {
316 0 : if (mCallback) {
317 0 : mCallback->EditorSpellCheckDone();
318 0 : mCallback = nullptr;
319 : }
320 0 : return NS_OK;
321 : }
322 :
323 : private:
324 : nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
325 : };
326 :
327 : NS_IMETHODIMP
328 0 : nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback)
329 : {
330 0 : NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
331 0 : mEditor = aEditor;
332 :
333 0 : nsCOMPtr<nsIDOMDocument> domDoc;
334 0 : mEditor->GetDocument(getter_AddRefs(domDoc));
335 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
336 0 : NS_ENSURE_STATE(doc);
337 :
338 : nsresult rv;
339 :
340 : // We can spell check with any editor type
341 : nsCOMPtr<nsITextServicesDocument>tsDoc =
342 0 : do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
343 0 : NS_ENSURE_SUCCESS(rv, rv);
344 :
345 0 : NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
346 :
347 0 : tsDoc->SetFilter(mTxtSrvFilter);
348 :
349 : // Pass the editor to the text services document
350 0 : rv = tsDoc->InitWithEditor(aEditor);
351 0 : NS_ENSURE_SUCCESS(rv, rv);
352 :
353 0 : if (aEnableSelectionChecking) {
354 : // Find out if the section is collapsed or not.
355 : // If it isn't, we want to spellcheck just the selection.
356 :
357 0 : nsCOMPtr<nsISelection> domSelection;
358 0 : aEditor->GetSelection(getter_AddRefs(domSelection));
359 0 : if (NS_WARN_IF(!domSelection)) {
360 0 : return NS_ERROR_FAILURE;
361 : }
362 0 : RefPtr<Selection> selection = domSelection->AsSelection();
363 :
364 0 : int32_t count = 0;
365 :
366 0 : rv = selection->GetRangeCount(&count);
367 0 : NS_ENSURE_SUCCESS(rv, rv);
368 :
369 0 : if (count > 0) {
370 0 : RefPtr<nsRange> range = selection->GetRangeAt(0);
371 0 : NS_ENSURE_STATE(range);
372 :
373 0 : bool collapsed = false;
374 0 : rv = range->GetCollapsed(&collapsed);
375 0 : NS_ENSURE_SUCCESS(rv, rv);
376 :
377 0 : if (!collapsed) {
378 : // We don't want to touch the range in the selection,
379 : // so create a new copy of it.
380 :
381 0 : RefPtr<nsRange> rangeBounds = range->CloneRange();
382 :
383 : // Make sure the new range spans complete words.
384 :
385 0 : rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds);
386 0 : NS_ENSURE_SUCCESS(rv, rv);
387 :
388 : // Now tell the text services that you only want
389 : // to iterate over the text in this range.
390 :
391 0 : rv = tsDoc->SetExtent(rangeBounds);
392 0 : NS_ENSURE_SUCCESS(rv, rv);
393 : }
394 : }
395 : }
396 :
397 0 : mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
398 0 : NS_ENSURE_SUCCESS(rv, rv);
399 :
400 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
401 :
402 0 : rv = mSpellChecker->SetDocument(tsDoc, true);
403 0 : NS_ENSURE_SUCCESS(rv, rv);
404 :
405 : // do not fail if UpdateCurrentDictionary fails because this method may
406 : // succeed later.
407 0 : rv = UpdateCurrentDictionary(aCallback);
408 0 : if (NS_FAILED(rv) && aCallback) {
409 : // However, if it does fail, we still need to call the callback since we
410 : // discard the failure. Do it asynchronously so that the caller is always
411 : // guaranteed async behavior.
412 0 : RefPtr<CallbackCaller> caller = new CallbackCaller(aCallback);
413 0 : rv = doc->Dispatch("nsEditorSpellCheck::CallbackCaller",
414 0 : TaskCategory::Other, caller.forget());
415 0 : NS_ENSURE_SUCCESS(rv, rv);
416 : }
417 :
418 0 : return NS_OK;
419 : }
420 :
421 : NS_IMETHODIMP
422 0 : nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord)
423 : {
424 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
425 :
426 0 : nsAutoString nextMisspelledWord;
427 :
428 0 : DeleteSuggestedWordList();
429 : // Beware! This may flush notifications via synchronous
430 : // ScrollSelectionIntoView.
431 0 : nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord,
432 0 : &mSuggestedWordList);
433 :
434 0 : *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord);
435 0 : return rv;
436 : }
437 :
438 : NS_IMETHODIMP
439 0 : nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord)
440 : {
441 0 : nsAutoString word;
442 : // XXX This is buggy if mSuggestedWordList.Length() is over INT32_MAX.
443 0 : if (mSuggestedWordIndex < static_cast<int32_t>(mSuggestedWordList.Length())) {
444 0 : *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]);
445 0 : mSuggestedWordIndex++;
446 : } else {
447 : // A blank string signals that there are no more strings
448 0 : *aSuggestedWord = ToNewUnicode(EmptyString());
449 : }
450 0 : return NS_OK;
451 : }
452 :
453 : NS_IMETHODIMP
454 0 : nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord,
455 : bool *aIsMisspelled)
456 : {
457 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
458 :
459 0 : DeleteSuggestedWordList();
460 0 : return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
461 0 : aIsMisspelled, &mSuggestedWordList);
462 : }
463 :
464 : NS_IMETHODIMP
465 0 : nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord,
466 : bool *aIsMisspelled)
467 : {
468 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
469 :
470 0 : return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
471 0 : aIsMisspelled, nullptr);
472 : }
473 :
474 : NS_IMETHODIMP
475 0 : nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord,
476 : const char16_t *aReplaceWord,
477 : bool allOccurrences)
478 : {
479 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
480 :
481 0 : return mSpellChecker->Replace(nsDependentString(aMisspelledWord),
482 0 : nsDependentString(aReplaceWord), allOccurrences);
483 : }
484 :
485 : NS_IMETHODIMP
486 0 : nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord)
487 : {
488 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
489 :
490 0 : return mSpellChecker->IgnoreAll(nsDependentString(aWord));
491 : }
492 :
493 : NS_IMETHODIMP
494 0 : nsEditorSpellCheck::GetPersonalDictionary()
495 : {
496 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
497 :
498 : // We can spell check with any editor type
499 0 : mDictionaryList.Clear();
500 0 : mDictionaryIndex = 0;
501 0 : return mSpellChecker->GetPersonalDictionary(&mDictionaryList);
502 : }
503 :
504 : NS_IMETHODIMP
505 0 : nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord)
506 : {
507 : // XXX This is buggy if mDictionaryList.Length() is over INT32_MAX.
508 0 : if (mDictionaryIndex < static_cast<int32_t>(mDictionaryList.Length())) {
509 0 : *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]);
510 0 : mDictionaryIndex++;
511 : } else {
512 : // A blank string signals that there are no more strings
513 0 : *aDictionaryWord = ToNewUnicode(EmptyString());
514 : }
515 :
516 0 : return NS_OK;
517 : }
518 :
519 : NS_IMETHODIMP
520 0 : nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord)
521 : {
522 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
523 :
524 0 : return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord));
525 : }
526 :
527 : NS_IMETHODIMP
528 0 : nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord)
529 : {
530 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
531 :
532 0 : return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord));
533 : }
534 :
535 : NS_IMETHODIMP
536 0 : nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount)
537 : {
538 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
539 :
540 0 : NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER);
541 :
542 0 : *aDictionaryList = 0;
543 0 : *aCount = 0;
544 :
545 0 : nsTArray<nsString> dictList;
546 :
547 0 : nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
548 :
549 0 : NS_ENSURE_SUCCESS(rv, rv);
550 :
551 0 : char16_t **tmpPtr = 0;
552 :
553 0 : if (dictList.IsEmpty()) {
554 : // If there are no dictionaries, return an array containing
555 : // one element and a count of one.
556 :
557 0 : tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *));
558 :
559 0 : NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
560 :
561 0 : *tmpPtr = 0;
562 0 : *aDictionaryList = tmpPtr;
563 0 : *aCount = 0;
564 :
565 0 : return NS_OK;
566 : }
567 :
568 0 : tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *) * dictList.Length());
569 :
570 0 : NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
571 :
572 0 : *aDictionaryList = tmpPtr;
573 0 : *aCount = dictList.Length();
574 :
575 0 : for (uint32_t i = 0; i < *aCount; i++) {
576 0 : tmpPtr[i] = ToNewUnicode(dictList[i]);
577 : }
578 :
579 0 : return rv;
580 : }
581 :
582 : NS_IMETHODIMP
583 0 : nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary)
584 : {
585 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
586 :
587 0 : return mSpellChecker->GetCurrentDictionary(aDictionary);
588 : }
589 :
590 : NS_IMETHODIMP
591 0 : nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
592 : {
593 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
594 :
595 0 : RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
596 :
597 : // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if
598 : // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us,
599 : // is on the stack. In other words: Only do this, if the user manually selected a
600 : // dictionary to use.
601 0 : if (!mUpdateDictionaryRunning) {
602 :
603 : // Ignore pending dictionary fetchers by increasing this number.
604 0 : mDictionaryFetcherGroup++;
605 :
606 0 : uint32_t flags = 0;
607 0 : mEditor->GetFlags(&flags);
608 0 : if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
609 0 : if (!aDictionary.IsEmpty() && (mPreferredLang.IsEmpty() ||
610 0 : !mPreferredLang.Equals(aDictionary,
611 0 : nsCaseInsensitiveStringComparator()))) {
612 : // When user sets dictionary manually, we store this value associated
613 : // with editor url, if it doesn't match the document language exactly.
614 : // For example on "en" sites, we need to store "en-GB", otherwise
615 : // the language might jump back to en-US although the user explicitly
616 : // chose otherwise.
617 0 : StoreCurrentDictionary(mEditor, aDictionary);
618 : #ifdef DEBUG_DICT
619 : printf("***** Writing content preferences for |%s|\n",
620 : NS_ConvertUTF16toUTF8(aDictionary).get());
621 : #endif
622 : } else {
623 : // If user sets a dictionary matching the language defined by
624 : // document, we consider content pref has been canceled, and we clear it.
625 0 : ClearCurrentDictionary(mEditor);
626 : #ifdef DEBUG_DICT
627 : printf("***** Clearing content preferences for |%s|\n",
628 : NS_ConvertUTF16toUTF8(aDictionary).get());
629 : #endif
630 : }
631 :
632 : // Also store it in as a preference, so we can use it as a fallback.
633 : // We don't want this for mail composer because it uses
634 : // "spellchecker.dictionary" as a preference.
635 0 : Preferences::SetString("spellchecker.dictionary", aDictionary);
636 : #ifdef DEBUG_DICT
637 : printf("***** Storing spellchecker.dictionary |%s|\n",
638 : NS_ConvertUTF16toUTF8(aDictionary).get());
639 : #endif
640 : }
641 : }
642 0 : return mSpellChecker->SetCurrentDictionary(aDictionary);
643 : }
644 :
645 : NS_IMETHODIMP
646 0 : nsEditorSpellCheck::UninitSpellChecker()
647 : {
648 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
649 :
650 : // Cleanup - kill the spell checker
651 0 : DeleteSuggestedWordList();
652 0 : mDictionaryList.Clear();
653 0 : mDictionaryIndex = 0;
654 0 : mSpellChecker = nullptr;
655 0 : return NS_OK;
656 : }
657 :
658 :
659 : NS_IMETHODIMP
660 0 : nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter)
661 : {
662 0 : mTxtSrvFilter = filter;
663 0 : return NS_OK;
664 : }
665 :
666 : nsresult
667 0 : nsEditorSpellCheck::DeleteSuggestedWordList()
668 : {
669 0 : mSuggestedWordList.Clear();
670 0 : mSuggestedWordIndex = 0;
671 0 : return NS_OK;
672 : }
673 :
674 : NS_IMETHODIMP
675 0 : nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback)
676 : {
677 0 : if (NS_WARN_IF(!mSpellChecker)) {
678 0 : return NS_ERROR_NOT_INITIALIZED;
679 : }
680 :
681 : nsresult rv;
682 :
683 0 : RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
684 :
685 : // Get language with html5 algorithm
686 0 : nsCOMPtr<nsIContent> rootContent;
687 0 : nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
688 0 : if (htmlEditor) {
689 0 : rootContent = htmlEditor->GetActiveEditingHost();
690 : } else {
691 0 : nsCOMPtr<nsIDOMElement> rootElement;
692 0 : rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
693 0 : NS_ENSURE_SUCCESS(rv, rv);
694 0 : rootContent = do_QueryInterface(rootElement);
695 : }
696 :
697 : // Try to get topmost document's document element for embedded mail editor.
698 0 : uint32_t flags = 0;
699 0 : mEditor->GetFlags(&flags);
700 0 : if (flags & nsIPlaintextEditor::eEditorMailMask) {
701 0 : nsCOMPtr<nsIDocument> ownerDoc = rootContent->OwnerDoc();
702 0 : NS_ENSURE_TRUE(ownerDoc, NS_ERROR_FAILURE);
703 0 : nsIDocument* parentDoc = ownerDoc->GetParentDocument();
704 0 : if (parentDoc) {
705 0 : rootContent = do_QueryInterface(parentDoc->GetDocumentElement());
706 : }
707 : }
708 :
709 0 : if (!rootContent) {
710 0 : return NS_ERROR_FAILURE;
711 : }
712 :
713 : RefPtr<DictionaryFetcher> fetcher =
714 0 : new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup);
715 0 : rootContent->GetLang(fetcher->mRootContentLang);
716 0 : nsCOMPtr<nsIDocument> doc = rootContent->GetUncomposedDoc();
717 0 : NS_ENSURE_STATE(doc);
718 0 : doc->GetContentLanguage(fetcher->mRootDocContentLang);
719 :
720 0 : rv = fetcher->Fetch(mEditor);
721 0 : NS_ENSURE_SUCCESS(rv, rv);
722 :
723 0 : return NS_OK;
724 : }
725 :
726 : // Helper function that iterates over the list of dictionaries and sets the one
727 : // that matches based on a given comparison type.
728 : void
729 0 : nsEditorSpellCheck::BuildDictionaryList(const nsAString& aDictName,
730 : const nsTArray<nsString>& aDictList,
731 : enum dictCompare aCompareType,
732 : nsTArray<nsString>& aOutList)
733 : {
734 0 : for (uint32_t i = 0; i < aDictList.Length(); i++) {
735 0 : nsAutoString dictStr(aDictList.ElementAt(i));
736 0 : bool equals = false;
737 0 : switch (aCompareType) {
738 : case DICT_NORMAL_COMPARE:
739 0 : equals = aDictName.Equals(dictStr);
740 0 : break;
741 : case DICT_COMPARE_CASE_INSENSITIVE:
742 0 : equals = aDictName.Equals(dictStr, nsCaseInsensitiveStringComparator());
743 0 : break;
744 : case DICT_COMPARE_DASHMATCH:
745 0 : equals = nsStyleUtil::DashMatchCompare(dictStr, aDictName, nsCaseInsensitiveStringComparator());
746 0 : break;
747 : }
748 0 : if (equals) {
749 0 : aOutList.AppendElement(dictStr);
750 : #ifdef DEBUG_DICT
751 : if (NS_SUCCEEDED(rv)) {
752 : printf("***** Trying |%s|.\n", NS_ConvertUTF16toUTF8(dictStr).get());
753 : }
754 : #endif
755 : // We always break here. We tried to set the dictionary to an existing
756 : // dictionary from the list. This must work, if it doesn't, there is
757 : // no point trying another one.
758 0 : return;
759 : }
760 : }
761 : }
762 :
763 : nsresult
764 0 : nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
765 : {
766 0 : MOZ_ASSERT(aFetcher);
767 0 : RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
768 :
769 0 : BeginUpdateDictionary();
770 :
771 0 : if (aFetcher->mGroup < mDictionaryFetcherGroup) {
772 : // SetCurrentDictionary was called after the fetch started. Don't overwrite
773 : // that dictionary with the fetched one.
774 0 : EndUpdateDictionary();
775 0 : if (aFetcher->mCallback) {
776 0 : aFetcher->mCallback->EditorSpellCheckDone();
777 : }
778 0 : return NS_OK;
779 : }
780 :
781 : /*
782 : * We try to derive the dictionary to use based on the following priorities:
783 : * 1) Content preference, so the language the user set for the site before.
784 : * (Introduced in bug 678842 and corrected in bug 717433.)
785 : * 2) Language set by the website, or any other dictionary that partly
786 : * matches that. (Introduced in bug 338427.)
787 : * Eg. if the website is "en-GB", a user who only has "en-US" will get
788 : * that. If the website is generic "en", the user will get one of the
789 : * "en-*" installed, (almost) at random.
790 : * However, we prefer what is stored in "spellchecker.dictionary",
791 : * so if the user chose "en-AU" before, they will get "en-AU" on a plain
792 : * "en" site. (Introduced in bug 682564.)
793 : * 3) The value of "spellchecker.dictionary" which reflects a previous
794 : * language choice of the user (on another site).
795 : * (This was the original behaviour before the aforementioned bugs
796 : * landed).
797 : * 4) The user's locale.
798 : * 5) Use the current dictionary that is currently set.
799 : * 6) The content of the "LANG" environment variable (if set).
800 : * 7) The first spell check dictionary installed.
801 : */
802 :
803 : // Get the language from the element or its closest parent according to:
804 : // https://html.spec.whatwg.org/#attr-lang
805 : // This is used in SetCurrentDictionary.
806 0 : mPreferredLang.Assign(aFetcher->mRootContentLang);
807 : #ifdef DEBUG_DICT
808 : printf("***** mPreferredLang (element) |%s|\n",
809 : NS_ConvertUTF16toUTF8(mPreferredLang).get());
810 : #endif
811 :
812 : // If no luck, try the "Content-Language" header.
813 0 : if (mPreferredLang.IsEmpty()) {
814 0 : mPreferredLang.Assign(aFetcher->mRootDocContentLang);
815 : #ifdef DEBUG_DICT
816 : printf("***** mPreferredLang (content-language) |%s|\n",
817 : NS_ConvertUTF16toUTF8(mPreferredLang).get());
818 : #endif
819 : }
820 :
821 : // We obtain a list of available dictionaries.
822 0 : AutoTArray<nsString, 8> dictList;
823 0 : nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
824 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
825 0 : EndUpdateDictionary();
826 0 : if (aFetcher->mCallback) {
827 0 : aFetcher->mCallback->EditorSpellCheckDone();
828 : }
829 0 : return rv;
830 : }
831 :
832 : // Priority 1:
833 : // If we successfully fetched a dictionary from content prefs, do not go
834 : // further. Use this exact dictionary.
835 : // Don't use content preferences for editor with eEditorMailMask flag.
836 0 : nsAutoString dictName;
837 : uint32_t flags;
838 0 : mEditor->GetFlags(&flags);
839 0 : if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
840 0 : dictName.Assign(aFetcher->mDictionary);
841 0 : if (!dictName.IsEmpty()) {
842 0 : AutoTArray<nsString, 1> tryDictList;
843 0 : BuildDictionaryList(dictName, dictList, DICT_NORMAL_COMPARE, tryDictList);
844 :
845 0 : RefPtr<nsEditorSpellCheck> self = this;
846 0 : RefPtr<DictionaryFetcher> fetcher = aFetcher;
847 0 : mSpellChecker->SetCurrentDictionaryFromList(tryDictList)->Then(
848 : GetMainThreadSerialEventTarget(),
849 : __func__,
850 0 : [self, fetcher]() {
851 : #ifdef DEBUG_DICT
852 : printf("***** Assigned from content preferences |%s|\n",
853 : NS_ConvertUTF16toUTF8(dictName).get());
854 : #endif
855 : // We take an early exit here, so let's not forget to clear the word
856 : // list.
857 0 : self->DeleteSuggestedWordList();
858 :
859 0 : self->EndUpdateDictionary();
860 0 : if (fetcher->mCallback) {
861 0 : fetcher->mCallback->EditorSpellCheckDone();
862 : }
863 0 : },
864 0 : [self, fetcher]() {
865 : // May be dictionary was uninstalled ?
866 : // Clear the content preference and continue.
867 0 : ClearCurrentDictionary(self->mEditor);
868 :
869 : // Priority 2 or later will handled by the following
870 0 : self->SetFallbackDictionary(fetcher);
871 0 : });
872 0 : return NS_OK;
873 : }
874 : }
875 0 : SetFallbackDictionary(aFetcher);
876 0 : return NS_OK;
877 : }
878 :
879 : void
880 0 : nsEditorSpellCheck::SetFallbackDictionary(DictionaryFetcher* aFetcher)
881 : {
882 0 : MOZ_ASSERT(mUpdateDictionaryRunning);
883 :
884 0 : AutoTArray<nsString, 6> tryDictList;
885 :
886 : // We obtain a list of available dictionaries.
887 0 : AutoTArray<nsString, 8> dictList;
888 0 : nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
889 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
890 0 : EndUpdateDictionary();
891 0 : if (aFetcher->mCallback) {
892 0 : aFetcher->mCallback->EditorSpellCheckDone();
893 : }
894 0 : return;
895 : }
896 :
897 : // Priority 2:
898 : // After checking the content preferences, we use the language of the element
899 : // or document.
900 0 : nsAutoString dictName(mPreferredLang);
901 : #ifdef DEBUG_DICT
902 : printf("***** Assigned from element/doc |%s|\n",
903 : NS_ConvertUTF16toUTF8(dictName).get());
904 : #endif
905 :
906 : // Get the preference value.
907 0 : nsAutoString preferredDict;
908 0 : preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary");
909 :
910 0 : if (!dictName.IsEmpty()) {
911 : // RFC 5646 explicitly states that matches should be case-insensitive.
912 : BuildDictionaryList(dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE,
913 0 : tryDictList);
914 :
915 : #ifdef DEBUG_DICT
916 : printf("***** Trying from element/doc |%s| \n",
917 : NS_ConvertUTF16toUTF8(dictName).get());
918 : #endif
919 :
920 : // Required dictionary was not available. Try to get a dictionary
921 : // matching at least language part of dictName.
922 0 : nsAutoString langCode;
923 0 : int32_t dashIdx = dictName.FindChar('-');
924 0 : if (dashIdx != -1) {
925 0 : langCode.Assign(Substring(dictName, 0, dashIdx));
926 : } else {
927 0 : langCode.Assign(dictName);
928 : }
929 :
930 : // Try dictionary.spellchecker preference, if it starts with langCode,
931 : // so we don't just get any random dictionary matching the language.
932 0 : if (!preferredDict.IsEmpty() &&
933 0 : nsStyleUtil::DashMatchCompare(preferredDict, langCode, nsDefaultStringComparator())) {
934 : #ifdef DEBUG_DICT
935 : printf("***** Trying preference value |%s| since it matches language code\n",
936 : NS_ConvertUTF16toUTF8(preferredDict).get());
937 : #endif
938 : BuildDictionaryList(preferredDict, dictList,
939 0 : DICT_COMPARE_CASE_INSENSITIVE, tryDictList);
940 : }
941 :
942 : // Use any dictionary with the required language.
943 : #ifdef DEBUG_DICT
944 : printf("***** Trying to find match for language code |%s|\n",
945 : NS_ConvertUTF16toUTF8(langCode).get());
946 : #endif
947 : BuildDictionaryList(langCode, dictList, DICT_COMPARE_DASHMATCH,
948 0 : tryDictList);
949 : }
950 :
951 : // Priority 3:
952 : // If the document didn't supply a dictionary or the setting failed,
953 : // try the user preference next.
954 0 : if (!preferredDict.IsEmpty()) {
955 : #ifdef DEBUG_DICT
956 : printf("***** Trying preference value |%s|\n",
957 : NS_ConvertUTF16toUTF8(preferredDict).get());
958 : #endif
959 : BuildDictionaryList(preferredDict, dictList, DICT_NORMAL_COMPARE,
960 0 : tryDictList);
961 : }
962 :
963 : // Priority 4:
964 : // As next fallback, try the current locale.
965 0 : nsAutoCString utf8DictName;
966 0 : LocaleService::GetInstance()->GetAppLocaleAsLangTag(utf8DictName);
967 :
968 0 : CopyUTF8toUTF16(utf8DictName, dictName);
969 : #ifdef DEBUG_DICT
970 : printf("***** Trying locale |%s|\n",
971 : NS_ConvertUTF16toUTF8(dictName).get());
972 : #endif
973 : BuildDictionaryList(dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE,
974 0 : tryDictList);
975 :
976 : // Priority 5:
977 : // If we have a current dictionary and we don't have no item in try list,
978 : // don't try anything else.
979 0 : nsAutoString currentDictionary;
980 0 : GetCurrentDictionary(currentDictionary);
981 0 : if (!currentDictionary.IsEmpty() && tryDictList.IsEmpty()) {
982 : #ifdef DEBUG_DICT
983 : printf("***** Retrieved current dict |%s|\n",
984 : NS_ConvertUTF16toUTF8(currentDictionary).get());
985 : #endif
986 0 : EndUpdateDictionary();
987 0 : if (aFetcher->mCallback) {
988 0 : aFetcher->mCallback->EditorSpellCheckDone();
989 : }
990 0 : return;
991 : }
992 :
993 : // Priority 6:
994 : // Try to get current dictionary from environment variable LANG.
995 : // LANG = language[_territory][.charset]
996 0 : char* env_lang = getenv("LANG");
997 0 : if (env_lang) {
998 0 : nsString lang = NS_ConvertUTF8toUTF16(env_lang);
999 : // Strip trailing charset, if there is any.
1000 0 : int32_t dot_pos = lang.FindChar('.');
1001 0 : if (dot_pos != -1) {
1002 0 : lang = Substring(lang, 0, dot_pos);
1003 : }
1004 :
1005 0 : int32_t underScore = lang.FindChar('_');
1006 0 : if (underScore != -1) {
1007 0 : lang.Replace(underScore, 1, '-');
1008 : #ifdef DEBUG_DICT
1009 : printf("***** Trying LANG from environment |%s|\n",
1010 : NS_ConvertUTF16toUTF8(lang).get());
1011 : #endif
1012 : BuildDictionaryList(lang, dictList, DICT_COMPARE_CASE_INSENSITIVE,
1013 0 : tryDictList);
1014 : }
1015 : }
1016 :
1017 : // Priority 7:
1018 : // If it does not work, pick the first one.
1019 0 : if (!dictList.IsEmpty()) {
1020 0 : BuildDictionaryList(dictList[0], dictList, DICT_NORMAL_COMPARE,
1021 0 : tryDictList);
1022 : #ifdef DEBUG_DICT
1023 : printf("***** Trying first of list |%s|\n",
1024 : NS_ConvertUTF16toUTF8(dictList[0]).get());
1025 : #endif
1026 : }
1027 :
1028 0 : RefPtr<nsEditorSpellCheck> self = this;
1029 0 : RefPtr<DictionaryFetcher> fetcher = aFetcher;
1030 0 : mSpellChecker->SetCurrentDictionaryFromList(tryDictList)->Then(
1031 : GetMainThreadSerialEventTarget(),
1032 : __func__,
1033 0 : [self, fetcher]() {
1034 : // If an error was thrown while setting the dictionary, just
1035 : // fail silently so that the spellchecker dialog is allowed to come
1036 : // up. The user can manually reset the language to their choice on
1037 : // the dialog if it is wrong.
1038 0 : self->DeleteSuggestedWordList();
1039 0 : self->EndUpdateDictionary();
1040 0 : if (fetcher->mCallback) {
1041 0 : fetcher->mCallback->EditorSpellCheckDone();
1042 : }
1043 0 : });
1044 : }
|