Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /******* BEGIN LICENSE BLOCK *******
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Initial Developers of the Original Code are Kevin Hendricks (MySpell)
15 * and László Németh (Hunspell). Portions created by the Initial Developers
16 * are Copyright (C) 2002-2005 the Initial Developers. All Rights Reserved.
17 *
18 * Contributor(s): Kevin Hendricks (kevin.hendricks@sympatico.ca)
19 * David Einstein (deinst@world.std.com)
20 * Michiel van Leeuwen (mvl@exedo.nl)
21 * Caolan McNamara (cmc@openoffice.org)
22 * László Németh (nemethl@gyorsposta.hu)
23 * Davide Prina
24 * Giuseppe Modugno
25 * Gianluca Turconi
26 * Simon Brouwer
27 * Noll Janos
28 * Biro Arpad
29 * Goldman Eleonora
30 * Sarlos Tamas
31 * Bencsath Boldizsar
32 * Halacsy Peter
33 * Dvornik Laszlo
34 * Gefferth Andras
35 * Nagy Viktor
36 * Varga Daniel
37 * Chris Halls
38 * Rene Engelhard
39 * Bram Moolenaar
40 * Dafydd Jones
41 * Harri Pitkanen
42 * Andras Timar
43 * Tor Lillqvist
44 * Jesper Kristensen (mail@jesperkristensen.dk)
45 *
46 * Alternatively, the contents of this file may be used under the terms of
47 * either the GNU General Public License Version 2 or later (the "GPL"), or
48 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
49 * in which case the provisions of the GPL or the LGPL are applicable instead
50 * of those above. If you wish to allow use of your version of this file only
51 * under the terms of either the GPL or the LGPL, and not to allow others to
52 * use your version of this file under the terms of the MPL, indicate your
53 * decision by deleting the provisions above and replace them with the notice
54 * and other provisions required by the GPL or the LGPL. If you do not delete
55 * the provisions above, a recipient may use your version of this file under
56 * the terms of any one of the MPL, the GPL or the LGPL.
57 *
58 ******* END LICENSE BLOCK *******/
60 #include "mozHunspell.h"
61 #include "nsReadableUtils.h"
62 #include "nsXPIDLString.h"
63 #include "nsIObserverService.h"
64 #include "nsISimpleEnumerator.h"
65 #include "nsIDirectoryEnumerator.h"
66 #include "nsIFile.h"
67 #include "nsDirectoryServiceUtils.h"
68 #include "nsDirectoryServiceDefs.h"
69 #include "mozISpellI18NManager.h"
70 #include "nsICharsetConverterManager.h"
71 #include "nsUnicharUtilCIID.h"
72 #include "nsUnicharUtils.h"
73 #include "nsCRT.h"
74 #include "mozInlineSpellChecker.h"
75 #include "mozilla/Services.h"
76 #include <stdlib.h>
77 #include "nsIPrefService.h"
78 #include "nsIPrefBranch.h"
80 static NS_DEFINE_CID(kUnicharUtilCID, NS_UNICHARUTIL_CID);
82 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozHunspell)
83 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozHunspell)
85 NS_INTERFACE_MAP_BEGIN(mozHunspell)
86 NS_INTERFACE_MAP_ENTRY(mozISpellCheckingEngine)
87 NS_INTERFACE_MAP_ENTRY(nsIObserver)
88 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
89 NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter)
90 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozISpellCheckingEngine)
91 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozHunspell)
92 NS_INTERFACE_MAP_END
94 NS_IMPL_CYCLE_COLLECTION(mozHunspell,
95 mPersonalDictionary,
96 mEncoder,
97 mDecoder)
99 template<> mozilla::Atomic<size_t> mozilla::CountingAllocatorBase<HunspellAllocator>::sAmount(0);
101 mozHunspell::mozHunspell()
102 : mHunspell(nullptr)
103 {
104 #ifdef DEBUG
105 // There must be only one instance of this class: it reports memory based on
106 // a single static count in HunspellAllocator.
107 static bool hasRun = false;
108 MOZ_ASSERT(!hasRun);
109 hasRun = true;
110 #endif
111 }
113 nsresult
114 mozHunspell::Init()
115 {
116 LoadDictionaryList();
118 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
119 if (obs) {
120 obs->AddObserver(this, "profile-do-change", true);
121 obs->AddObserver(this, "profile-after-change", true);
122 }
124 mozilla::RegisterWeakMemoryReporter(this);
126 return NS_OK;
127 }
129 mozHunspell::~mozHunspell()
130 {
131 mozilla::UnregisterWeakMemoryReporter(this);
133 mPersonalDictionary = nullptr;
134 delete mHunspell;
135 }
137 /* attribute wstring dictionary; */
138 NS_IMETHODIMP mozHunspell::GetDictionary(char16_t **aDictionary)
139 {
140 NS_ENSURE_ARG_POINTER(aDictionary);
142 *aDictionary = ToNewUnicode(mDictionary);
143 return *aDictionary ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
144 }
146 /* set the Dictionary.
147 * This also Loads the dictionary and initializes the converter using the dictionaries converter
148 */
149 NS_IMETHODIMP mozHunspell::SetDictionary(const char16_t *aDictionary)
150 {
151 NS_ENSURE_ARG_POINTER(aDictionary);
153 if (nsDependentString(aDictionary).IsEmpty()) {
154 delete mHunspell;
155 mHunspell = nullptr;
156 mDictionary.AssignLiteral("");
157 mAffixFileName.AssignLiteral("");
158 mLanguage.AssignLiteral("");
159 mDecoder = nullptr;
160 mEncoder = nullptr;
162 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
163 if (obs) {
164 obs->NotifyObservers(nullptr,
165 SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION,
166 nullptr);
167 }
168 return NS_OK;
169 }
171 nsIFile* affFile = mDictionaries.GetWeak(nsDependentString(aDictionary));
172 if (!affFile)
173 return NS_ERROR_FILE_NOT_FOUND;
175 nsAutoCString dictFileName, affFileName;
177 // XXX This isn't really good. nsIFile->NativePath isn't safe for all
178 // character sets on Windows.
179 // A better way would be to QI to nsIFile, and get a filehandle
180 // from there. Only problem is that hunspell wants a path
182 nsresult rv = affFile->GetNativePath(affFileName);
183 NS_ENSURE_SUCCESS(rv, rv);
185 if (mAffixFileName.Equals(affFileName.get()))
186 return NS_OK;
188 dictFileName = affFileName;
189 int32_t dotPos = dictFileName.RFindChar('.');
190 if (dotPos == -1)
191 return NS_ERROR_FAILURE;
193 dictFileName.SetLength(dotPos);
194 dictFileName.AppendLiteral(".dic");
196 // SetDictionary can be called multiple times, so we might have a
197 // valid mHunspell instance which needs cleaned up.
198 delete mHunspell;
200 mDictionary = aDictionary;
201 mAffixFileName = affFileName;
203 mHunspell = new Hunspell(affFileName.get(),
204 dictFileName.get());
205 if (!mHunspell)
206 return NS_ERROR_OUT_OF_MEMORY;
208 nsCOMPtr<nsICharsetConverterManager> ccm =
209 do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
210 NS_ENSURE_SUCCESS(rv, rv);
212 rv = ccm->GetUnicodeDecoder(mHunspell->get_dic_encoding(),
213 getter_AddRefs(mDecoder));
214 NS_ENSURE_SUCCESS(rv, rv);
216 rv = ccm->GetUnicodeEncoder(mHunspell->get_dic_encoding(),
217 getter_AddRefs(mEncoder));
218 NS_ENSURE_SUCCESS(rv, rv);
221 if (mEncoder)
222 mEncoder->SetOutputErrorBehavior(mEncoder->kOnError_Signal, nullptr, '?');
224 int32_t pos = mDictionary.FindChar('-');
225 if (pos == -1)
226 pos = mDictionary.FindChar('_');
228 if (pos == -1)
229 mLanguage.Assign(mDictionary);
230 else
231 mLanguage = Substring(mDictionary, 0, pos);
233 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
234 if (obs) {
235 obs->NotifyObservers(nullptr,
236 SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION,
237 nullptr);
238 }
240 return NS_OK;
241 }
243 /* readonly attribute wstring language; */
244 NS_IMETHODIMP mozHunspell::GetLanguage(char16_t **aLanguage)
245 {
246 NS_ENSURE_ARG_POINTER(aLanguage);
248 if (mDictionary.IsEmpty())
249 return NS_ERROR_NOT_INITIALIZED;
251 *aLanguage = ToNewUnicode(mLanguage);
252 return *aLanguage ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
253 }
255 /* readonly attribute boolean providesPersonalDictionary; */
256 NS_IMETHODIMP mozHunspell::GetProvidesPersonalDictionary(bool *aProvidesPersonalDictionary)
257 {
258 NS_ENSURE_ARG_POINTER(aProvidesPersonalDictionary);
260 *aProvidesPersonalDictionary = false;
261 return NS_OK;
262 }
264 /* readonly attribute boolean providesWordUtils; */
265 NS_IMETHODIMP mozHunspell::GetProvidesWordUtils(bool *aProvidesWordUtils)
266 {
267 NS_ENSURE_ARG_POINTER(aProvidesWordUtils);
269 *aProvidesWordUtils = false;
270 return NS_OK;
271 }
273 /* readonly attribute wstring name; */
274 NS_IMETHODIMP mozHunspell::GetName(char16_t * *aName)
275 {
276 return NS_ERROR_NOT_IMPLEMENTED;
277 }
279 /* readonly attribute wstring copyright; */
280 NS_IMETHODIMP mozHunspell::GetCopyright(char16_t * *aCopyright)
281 {
282 return NS_ERROR_NOT_IMPLEMENTED;
283 }
285 /* attribute mozIPersonalDictionary personalDictionary; */
286 NS_IMETHODIMP mozHunspell::GetPersonalDictionary(mozIPersonalDictionary * *aPersonalDictionary)
287 {
288 *aPersonalDictionary = mPersonalDictionary;
289 NS_IF_ADDREF(*aPersonalDictionary);
290 return NS_OK;
291 }
293 NS_IMETHODIMP mozHunspell::SetPersonalDictionary(mozIPersonalDictionary * aPersonalDictionary)
294 {
295 mPersonalDictionary = aPersonalDictionary;
296 return NS_OK;
297 }
299 struct AppendNewStruct
300 {
301 char16_t **dics;
302 uint32_t count;
303 bool failed;
304 };
306 static PLDHashOperator
307 AppendNewString(const nsAString& aString, nsIFile* aFile, void* aClosure)
308 {
309 AppendNewStruct *ans = (AppendNewStruct*) aClosure;
310 ans->dics[ans->count] = ToNewUnicode(aString);
311 if (!ans->dics[ans->count]) {
312 ans->failed = true;
313 return PL_DHASH_STOP;
314 }
316 ++ans->count;
317 return PL_DHASH_NEXT;
318 }
320 /* void GetDictionaryList ([array, size_is (count)] out wstring dictionaries, out uint32_t count); */
321 NS_IMETHODIMP mozHunspell::GetDictionaryList(char16_t ***aDictionaries,
322 uint32_t *aCount)
323 {
324 if (!aDictionaries || !aCount)
325 return NS_ERROR_NULL_POINTER;
327 AppendNewStruct ans = {
328 (char16_t**) NS_Alloc(sizeof(char16_t*) * mDictionaries.Count()),
329 0,
330 false
331 };
333 // This pointer is used during enumeration
334 mDictionaries.EnumerateRead(AppendNewString, &ans);
336 if (ans.failed) {
337 while (ans.count) {
338 --ans.count;
339 NS_Free(ans.dics[ans.count]);
340 }
341 NS_Free(ans.dics);
342 return NS_ERROR_OUT_OF_MEMORY;
343 }
345 *aDictionaries = ans.dics;
346 *aCount = ans.count;
348 return NS_OK;
349 }
351 void
352 mozHunspell::LoadDictionaryList()
353 {
354 mDictionaries.Clear();
356 nsresult rv;
358 nsCOMPtr<nsIProperties> dirSvc =
359 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
360 if (!dirSvc)
361 return;
363 // find built in dictionaries, or dictionaries specified in
364 // spellchecker.dictionary_path in prefs
365 nsCOMPtr<nsIFile> dictDir;
367 // check preferences first
368 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
369 if (prefs) {
370 nsCString extDictPath;
371 rv = prefs->GetCharPref("spellchecker.dictionary_path", getter_Copies(extDictPath));
372 if (NS_SUCCEEDED(rv)) {
373 // set the spellchecker.dictionary_path
374 rv = NS_NewNativeLocalFile(extDictPath, true, getter_AddRefs(dictDir));
375 }
376 }
377 if (!dictDir) {
378 // spellcheck.dictionary_path not found, set internal path
379 rv = dirSvc->Get(DICTIONARY_SEARCH_DIRECTORY,
380 NS_GET_IID(nsIFile), getter_AddRefs(dictDir));
381 }
382 if (dictDir) {
383 LoadDictionariesFromDir(dictDir);
384 }
385 else {
386 // try to load gredir/dictionaries
387 nsCOMPtr<nsIFile> greDir;
388 rv = dirSvc->Get(NS_GRE_DIR,
389 NS_GET_IID(nsIFile), getter_AddRefs(greDir));
390 if (NS_SUCCEEDED(rv)) {
391 greDir->AppendNative(NS_LITERAL_CSTRING("dictionaries"));
392 LoadDictionariesFromDir(greDir);
393 }
395 // try to load appdir/dictionaries only if different than gredir
396 nsCOMPtr<nsIFile> appDir;
397 rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR,
398 NS_GET_IID(nsIFile), getter_AddRefs(appDir));
399 bool equals;
400 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(appDir->Equals(greDir, &equals)) && !equals) {
401 appDir->AppendNative(NS_LITERAL_CSTRING("dictionaries"));
402 LoadDictionariesFromDir(appDir);
403 }
404 }
406 // find dictionaries from extensions requiring restart
407 nsCOMPtr<nsISimpleEnumerator> dictDirs;
408 rv = dirSvc->Get(DICTIONARY_SEARCH_DIRECTORY_LIST,
409 NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dictDirs));
410 if (NS_FAILED(rv))
411 return;
413 bool hasMore;
414 while (NS_SUCCEEDED(dictDirs->HasMoreElements(&hasMore)) && hasMore) {
415 nsCOMPtr<nsISupports> elem;
416 dictDirs->GetNext(getter_AddRefs(elem));
418 dictDir = do_QueryInterface(elem);
419 if (dictDir)
420 LoadDictionariesFromDir(dictDir);
421 }
423 // find dictionaries from restartless extensions
424 for (int32_t i = 0; i < mDynamicDirectories.Count(); i++) {
425 LoadDictionariesFromDir(mDynamicDirectories[i]);
426 }
428 // Now we have finished updating the list of dictionaries, update the current
429 // dictionary and any editors which may use it.
430 mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking();
432 // Check if the current dictionary is still available.
433 // If not, try to replace it with another dictionary of the same language.
434 if (!mDictionary.IsEmpty()) {
435 rv = SetDictionary(mDictionary.get());
436 if (NS_SUCCEEDED(rv))
437 return;
438 }
440 // If the current dictionary has gone, and we don't have a good replacement,
441 // set no current dictionary.
442 if (!mDictionary.IsEmpty()) {
443 SetDictionary(EmptyString().get());
444 }
445 }
447 NS_IMETHODIMP
448 mozHunspell::LoadDictionariesFromDir(nsIFile* aDir)
449 {
450 nsresult rv;
452 bool check = false;
453 rv = aDir->Exists(&check);
454 if (NS_FAILED(rv) || !check)
455 return NS_ERROR_UNEXPECTED;
457 rv = aDir->IsDirectory(&check);
458 if (NS_FAILED(rv) || !check)
459 return NS_ERROR_UNEXPECTED;
461 nsCOMPtr<nsISimpleEnumerator> e;
462 rv = aDir->GetDirectoryEntries(getter_AddRefs(e));
463 if (NS_FAILED(rv))
464 return NS_ERROR_UNEXPECTED;
466 nsCOMPtr<nsIDirectoryEnumerator> files(do_QueryInterface(e));
467 if (!files)
468 return NS_ERROR_UNEXPECTED;
470 nsCOMPtr<nsIFile> file;
471 while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
472 nsAutoString leafName;
473 file->GetLeafName(leafName);
474 if (!StringEndsWith(leafName, NS_LITERAL_STRING(".dic")))
475 continue;
477 nsAutoString dict(leafName);
478 dict.SetLength(dict.Length() - 4); // magic length of ".dic"
480 // check for the presence of the .aff file
481 leafName = dict;
482 leafName.AppendLiteral(".aff");
483 file->SetLeafName(leafName);
484 rv = file->Exists(&check);
485 if (NS_FAILED(rv) || !check)
486 continue;
488 #ifdef DEBUG_bsmedberg
489 printf("Adding dictionary: %s\n", NS_ConvertUTF16toUTF8(dict).get());
490 #endif
492 mDictionaries.Put(dict, file);
493 }
495 return NS_OK;
496 }
498 nsresult mozHunspell::ConvertCharset(const char16_t* aStr, char ** aDst)
499 {
500 NS_ENSURE_ARG_POINTER(aDst);
501 NS_ENSURE_TRUE(mEncoder, NS_ERROR_NULL_POINTER);
503 int32_t outLength;
504 int32_t inLength = NS_strlen(aStr);
505 nsresult rv = mEncoder->GetMaxLength(aStr, inLength, &outLength);
506 NS_ENSURE_SUCCESS(rv, rv);
508 *aDst = (char *) nsMemory::Alloc(sizeof(char) * (outLength+1));
509 NS_ENSURE_TRUE(*aDst, NS_ERROR_OUT_OF_MEMORY);
511 rv = mEncoder->Convert(aStr, &inLength, *aDst, &outLength);
512 if (NS_SUCCEEDED(rv))
513 (*aDst)[outLength] = '\0';
515 return rv;
516 }
518 /* boolean Check (in wstring word); */
519 NS_IMETHODIMP mozHunspell::Check(const char16_t *aWord, bool *aResult)
520 {
521 NS_ENSURE_ARG_POINTER(aWord);
522 NS_ENSURE_ARG_POINTER(aResult);
523 NS_ENSURE_TRUE(mHunspell, NS_ERROR_FAILURE);
525 nsXPIDLCString charsetWord;
526 nsresult rv = ConvertCharset(aWord, getter_Copies(charsetWord));
527 NS_ENSURE_SUCCESS(rv, rv);
529 *aResult = !!mHunspell->spell(charsetWord);
532 if (!*aResult && mPersonalDictionary)
533 rv = mPersonalDictionary->Check(aWord, mLanguage.get(), aResult);
535 return rv;
536 }
538 /* void Suggest (in wstring word, [array, size_is (count)] out wstring suggestions, out uint32_t count); */
539 NS_IMETHODIMP mozHunspell::Suggest(const char16_t *aWord, char16_t ***aSuggestions, uint32_t *aSuggestionCount)
540 {
541 NS_ENSURE_ARG_POINTER(aSuggestions);
542 NS_ENSURE_ARG_POINTER(aSuggestionCount);
543 NS_ENSURE_TRUE(mHunspell, NS_ERROR_FAILURE);
545 nsresult rv;
546 *aSuggestionCount = 0;
548 nsXPIDLCString charsetWord;
549 rv = ConvertCharset(aWord, getter_Copies(charsetWord));
550 NS_ENSURE_SUCCESS(rv, rv);
552 char ** wlst;
553 *aSuggestionCount = mHunspell->suggest(&wlst, charsetWord);
555 if (*aSuggestionCount) {
556 *aSuggestions = (char16_t **)nsMemory::Alloc(*aSuggestionCount * sizeof(char16_t *));
557 if (*aSuggestions) {
558 uint32_t index = 0;
559 for (index = 0; index < *aSuggestionCount && NS_SUCCEEDED(rv); ++index) {
560 // Convert the suggestion to utf16
561 int32_t inLength = strlen(wlst[index]);
562 int32_t outLength;
563 rv = mDecoder->GetMaxLength(wlst[index], inLength, &outLength);
564 if (NS_SUCCEEDED(rv))
565 {
566 (*aSuggestions)[index] = (char16_t *) nsMemory::Alloc(sizeof(char16_t) * (outLength+1));
567 if ((*aSuggestions)[index])
568 {
569 rv = mDecoder->Convert(wlst[index], &inLength, (*aSuggestions)[index], &outLength);
570 if (NS_SUCCEEDED(rv))
571 (*aSuggestions)[index][outLength] = 0;
572 }
573 else
574 rv = NS_ERROR_OUT_OF_MEMORY;
575 }
576 }
578 if (NS_FAILED(rv))
579 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(index, *aSuggestions); // free the char16_t strings up to the point at which the error occurred
580 }
581 else // if (*aSuggestions)
582 rv = NS_ERROR_OUT_OF_MEMORY;
583 }
585 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(*aSuggestionCount, wlst);
586 return rv;
587 }
589 NS_IMETHODIMP
590 mozHunspell::Observe(nsISupports* aSubj, const char *aTopic,
591 const char16_t *aData)
592 {
593 NS_ASSERTION(!strcmp(aTopic, "profile-do-change")
594 || !strcmp(aTopic, "profile-after-change"),
595 "Unexpected observer topic");
597 LoadDictionaryList();
599 return NS_OK;
600 }
602 /* void addDirectory(in nsIFile dir); */
603 NS_IMETHODIMP mozHunspell::AddDirectory(nsIFile *aDir)
604 {
605 mDynamicDirectories.AppendObject(aDir);
606 LoadDictionaryList();
607 return NS_OK;
608 }
610 /* void removeDirectory(in nsIFile dir); */
611 NS_IMETHODIMP mozHunspell::RemoveDirectory(nsIFile *aDir)
612 {
613 mDynamicDirectories.RemoveObject(aDir);
614 LoadDictionaryList();
615 return NS_OK;
616 }