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