1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsHyphenationManager.h"
7 #include "nsHyphenator.h"
8 #include "nsAtom.h"
9 #include "nsIFile.h"
10 #include "nsIURI.h"
11 #include "nsIProperties.h"
12 #include "nsISimpleEnumerator.h"
13 #include "nsIDirectoryEnumerator.h"
14 #include "nsDirectoryServiceDefs.h"
15 #include "nsNetUtil.h"
16 #include "nsUnicharUtils.h"
17 #include "mozilla/Preferences.h"
18 #include "nsZipArchive.h"
19 #include "mozilla/Services.h"
20 #include "nsIObserverService.h"
21 #include "nsCRT.h"
22 #include "nsAppDirectoryServiceDefs.h"
23 #include "nsDirectoryServiceUtils.h"
24 #include "nsMemory.h"
25 
26 using namespace mozilla;
27 
28 static const char kIntlHyphenationAliasPrefix[] = "intl.hyphenation-alias.";
29 static const char kMemoryPressureNotification[] = "memory-pressure";
30 
31 nsHyphenationManager *nsHyphenationManager::sInstance = nullptr;
32 
NS_IMPL_ISUPPORTS(nsHyphenationManager::MemoryPressureObserver,nsIObserver)33 NS_IMPL_ISUPPORTS(nsHyphenationManager::MemoryPressureObserver, nsIObserver)
34 
35 NS_IMETHODIMP
36 nsHyphenationManager::MemoryPressureObserver::Observe(nsISupports *aSubject,
37                                                       const char *aTopic,
38                                                       const char16_t *aData) {
39   if (!nsCRT::strcmp(aTopic, kMemoryPressureNotification)) {
40     // We don't call Instance() here, as we don't want to create a hyphenation
41     // manager if there isn't already one in existence.
42     // (This observer class is local to the hyphenation manager, so it can use
43     // the protected members directly.)
44     if (nsHyphenationManager::sInstance) {
45       nsHyphenationManager::sInstance->mHyphenators.Clear();
46     }
47   }
48   return NS_OK;
49 }
50 
Instance()51 nsHyphenationManager *nsHyphenationManager::Instance() {
52   if (sInstance == nullptr) {
53     sInstance = new nsHyphenationManager();
54 
55     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
56     if (obs) {
57       obs->AddObserver(new MemoryPressureObserver, kMemoryPressureNotification,
58                        false);
59     }
60   }
61   return sInstance;
62 }
63 
Shutdown()64 void nsHyphenationManager::Shutdown() {
65   delete sInstance;
66   sInstance = nullptr;
67 }
68 
nsHyphenationManager()69 nsHyphenationManager::nsHyphenationManager() {
70   LoadPatternList();
71   LoadAliases();
72 }
73 
~nsHyphenationManager()74 nsHyphenationManager::~nsHyphenationManager() { sInstance = nullptr; }
75 
GetHyphenator(nsAtom * aLocale)76 already_AddRefed<nsHyphenator> nsHyphenationManager::GetHyphenator(
77     nsAtom *aLocale) {
78   RefPtr<nsHyphenator> hyph;
79   mHyphenators.Get(aLocale, getter_AddRefs(hyph));
80   if (hyph) {
81     return hyph.forget();
82   }
83   nsCOMPtr<nsIURI> uri = mPatternFiles.Get(aLocale);
84   if (!uri) {
85     RefPtr<nsAtom> alias = mHyphAliases.Get(aLocale);
86     if (alias) {
87       mHyphenators.Get(alias, getter_AddRefs(hyph));
88       if (hyph) {
89         return hyph.forget();
90       }
91       uri = mPatternFiles.Get(alias);
92       if (uri) {
93         aLocale = alias;
94       }
95     }
96     if (!uri) {
97       // In the case of a locale such as "de-DE-1996", we try replacing
98       // successive trailing subtags with "-*" to find fallback patterns,
99       // so "de-DE-1996" -> "de-DE-*" (and then recursively -> "de-*")
100       nsAtomCString localeStr(aLocale);
101       if (StringEndsWith(localeStr, NS_LITERAL_CSTRING("-*"))) {
102         localeStr.Truncate(localeStr.Length() - 2);
103       }
104       int32_t i = localeStr.RFindChar('-');
105       if (i > 1) {
106         localeStr.ReplaceLiteral(i, localeStr.Length() - i, "-*");
107         RefPtr<nsAtom> fuzzyLocale = NS_Atomize(localeStr);
108         return GetHyphenator(fuzzyLocale);
109       } else {
110         return nullptr;
111       }
112     }
113   }
114   hyph = new nsHyphenator(uri);
115   if (hyph->IsValid()) {
116     mHyphenators.Put(aLocale, hyph);
117     return hyph.forget();
118   }
119 #ifdef DEBUG
120   nsCString msg("failed to load patterns from ");
121   msg += uri->GetSpecOrDefault();
122   NS_WARNING(msg.get());
123 #endif
124   mPatternFiles.Remove(aLocale);
125   return nullptr;
126 }
127 
LoadPatternList()128 void nsHyphenationManager::LoadPatternList() {
129   mPatternFiles.Clear();
130   mHyphenators.Clear();
131 
132   LoadPatternListFromOmnijar(Omnijar::GRE);
133   LoadPatternListFromOmnijar(Omnijar::APP);
134 
135   nsCOMPtr<nsIProperties> dirSvc =
136       do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
137   if (!dirSvc) {
138     return;
139   }
140 
141   nsresult rv;
142   nsCOMPtr<nsIFile> greDir;
143   rv = dirSvc->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir));
144   if (NS_SUCCEEDED(rv)) {
145     greDir->AppendNative(NS_LITERAL_CSTRING("hyphenation"));
146     LoadPatternListFromDir(greDir);
147   }
148 
149   nsCOMPtr<nsIFile> appDir;
150   rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
151                    getter_AddRefs(appDir));
152   if (NS_SUCCEEDED(rv)) {
153     appDir->AppendNative(NS_LITERAL_CSTRING("hyphenation"));
154     bool equals;
155     if (NS_SUCCEEDED(appDir->Equals(greDir, &equals)) && !equals) {
156       LoadPatternListFromDir(appDir);
157     }
158   }
159 
160   nsCOMPtr<nsIFile> profileDir;
161   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
162                               getter_AddRefs(profileDir));
163   if (NS_SUCCEEDED(rv)) {
164     profileDir->AppendNative(NS_LITERAL_CSTRING("hyphenation"));
165     LoadPatternListFromDir(profileDir);
166   }
167 }
168 
LoadPatternListFromOmnijar(Omnijar::Type aType)169 void nsHyphenationManager::LoadPatternListFromOmnijar(Omnijar::Type aType) {
170   nsCString base;
171   nsresult rv = Omnijar::GetURIString(aType, base);
172   if (NS_FAILED(rv)) {
173     return;
174   }
175 
176   RefPtr<nsZipArchive> zip = Omnijar::GetReader(aType);
177   if (!zip) {
178     return;
179   }
180 
181   nsZipFind *find;
182   zip->FindInit("hyphenation/hyph_*.dic", &find);
183   if (!find) {
184     return;
185   }
186 
187   const char *result;
188   uint16_t len;
189   while (NS_SUCCEEDED(find->FindNext(&result, &len))) {
190     nsCString uriString(base);
191     uriString.Append(result, len);
192     nsCOMPtr<nsIURI> uri;
193     rv = NS_NewURI(getter_AddRefs(uri), uriString);
194     if (NS_FAILED(rv)) {
195       continue;
196     }
197     nsCString locale;
198     rv = uri->GetPathQueryRef(locale);
199     if (NS_FAILED(rv)) {
200       continue;
201     }
202     ToLowerCase(locale);
203     locale.SetLength(locale.Length() - 4);     // strip ".dic"
204     locale.Cut(0, locale.RFindChar('/') + 1);  // strip directory
205     if (StringBeginsWith(locale, NS_LITERAL_CSTRING("hyph_"))) {
206       locale.Cut(0, 5);
207     }
208     for (uint32_t i = 0; i < locale.Length(); ++i) {
209       if (locale[i] == '_') {
210         locale.Replace(i, 1, '-');
211       }
212     }
213     RefPtr<nsAtom> localeAtom = NS_Atomize(locale);
214     if (NS_SUCCEEDED(rv)) {
215       mPatternFiles.Put(localeAtom, uri);
216     }
217   }
218 
219   delete find;
220 }
221 
LoadPatternListFromDir(nsIFile * aDir)222 void nsHyphenationManager::LoadPatternListFromDir(nsIFile *aDir) {
223   nsresult rv;
224 
225   bool check = false;
226   rv = aDir->Exists(&check);
227   if (NS_FAILED(rv) || !check) {
228     return;
229   }
230 
231   rv = aDir->IsDirectory(&check);
232   if (NS_FAILED(rv) || !check) {
233     return;
234   }
235 
236   nsCOMPtr<nsISimpleEnumerator> e;
237   rv = aDir->GetDirectoryEntries(getter_AddRefs(e));
238   if (NS_FAILED(rv)) {
239     return;
240   }
241 
242   nsCOMPtr<nsIDirectoryEnumerator> files(do_QueryInterface(e));
243   if (!files) {
244     return;
245   }
246 
247   nsCOMPtr<nsIFile> file;
248   while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
249     nsAutoString dictName;
250     file->GetLeafName(dictName);
251     NS_ConvertUTF16toUTF8 locale(dictName);
252     ToLowerCase(locale);
253     if (!StringEndsWith(locale, NS_LITERAL_CSTRING(".dic"))) {
254       continue;
255     }
256     if (StringBeginsWith(locale, NS_LITERAL_CSTRING("hyph_"))) {
257       locale.Cut(0, 5);
258     }
259     locale.SetLength(locale.Length() - 4);  // strip ".dic"
260     for (uint32_t i = 0; i < locale.Length(); ++i) {
261       if (locale[i] == '_') {
262         locale.Replace(i, 1, '-');
263       }
264     }
265 #ifdef DEBUG_hyph
266     printf("adding hyphenation patterns for %s: %s\n", locale.get(),
267            NS_ConvertUTF16toUTF8(dictName).get());
268 #endif
269     RefPtr<nsAtom> localeAtom = NS_Atomize(locale);
270     nsCOMPtr<nsIURI> uri;
271     nsresult rv = NS_NewFileURI(getter_AddRefs(uri), file);
272     if (NS_SUCCEEDED(rv)) {
273       mPatternFiles.Put(localeAtom, uri);
274     }
275   }
276 }
277 
LoadAliases()278 void nsHyphenationManager::LoadAliases() {
279   nsIPrefBranch *prefRootBranch = Preferences::GetRootBranch();
280   if (!prefRootBranch) {
281     return;
282   }
283   uint32_t prefCount;
284   char **prefNames;
285   nsresult rv = prefRootBranch->GetChildList(kIntlHyphenationAliasPrefix,
286                                              &prefCount, &prefNames);
287   if (NS_SUCCEEDED(rv) && prefCount > 0) {
288     for (uint32_t i = 0; i < prefCount; ++i) {
289       nsAutoCString value;
290       rv = Preferences::GetCString(prefNames[i], value);
291       if (NS_SUCCEEDED(rv)) {
292         nsAutoCString alias(prefNames[i]);
293         alias.Cut(0, sizeof(kIntlHyphenationAliasPrefix) - 1);
294         ToLowerCase(alias);
295         ToLowerCase(value);
296         RefPtr<nsAtom> aliasAtom = NS_Atomize(alias);
297         RefPtr<nsAtom> valueAtom = NS_Atomize(value);
298         mHyphAliases.Put(aliasAtom, valueAtom);
299       }
300     }
301     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefNames);
302   }
303 }
304