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 "nsIJARURI.h"
12 #include "nsIProperties.h"
13 #include "nsIDirectoryEnumerator.h"
14 #include "nsDirectoryServiceDefs.h"
15 #include "nsNetUtil.h"
16 #include "nsUnicharUtils.h"
17 #include "mozilla/CountingAllocatorBase.h"
18 #include "mozilla/Preferences.h"
19 #include "nsZipArchive.h"
20 #include "mozilla/Services.h"
21 #include "nsIObserverService.h"
22 #include "nsCRT.h"
23 #include "nsAppDirectoryServiceDefs.h"
24 #include "nsDirectoryServiceUtils.h"
25 #include "nsMemory.h"
26 #include "nsXULAppAPI.h"
27 
28 using namespace mozilla;
29 
30 static const char kIntlHyphenationAliasPrefix[] = "intl.hyphenation-alias.";
31 static const char kMemoryPressureNotification[] = "memory-pressure";
32 
33 class HyphenReporter final : public nsIMemoryReporter {
34  private:
35   ~HyphenReporter() = default;
36 
37  public:
38   NS_DECL_ISUPPORTS
39 
40   // For telemetry, we report the memory rounded up to the nearest KB.
MemoryAllocatedInKB()41   static uint32_t MemoryAllocatedInKB() {
42     size_t total = 0;
43     if (nsHyphenationManager::Instance()) {
44       total = nsHyphenationManager::Instance()->SizeOfIncludingThis(
45           moz_malloc_size_of);
46     }
47     return (total + 1023) / 1024;
48   }
49 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)50   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
51                             nsISupports* aData, bool aAnonymize) override {
52     size_t total = 0;
53     if (nsHyphenationManager::Instance()) {
54       total = nsHyphenationManager::Instance()->SizeOfIncludingThis(
55           moz_malloc_size_of);
56     }
57     MOZ_COLLECT_REPORT("explicit/hyphenation", KIND_HEAP, UNITS_BYTES, total,
58                        "Memory used by hyphenation data.");
59     return NS_OK;
60   }
61 };
62 
63 NS_IMPL_ISUPPORTS(HyphenReporter, nsIMemoryReporter)
64 
65 nsHyphenationManager* nsHyphenationManager::sInstance = nullptr;
66 
NS_IMPL_ISUPPORTS(nsHyphenationManager,nsIObserver)67 NS_IMPL_ISUPPORTS(nsHyphenationManager, nsIObserver)
68 
69 NS_IMETHODIMP
70 nsHyphenationManager::Observe(nsISupports* aSubject, const char* aTopic,
71                               const char16_t* aData) {
72   if (!nsCRT::strcmp(aTopic, kMemoryPressureNotification)) {
73     nsHyphenationManager::sInstance->mHyphenators.Clear();
74   }
75   return NS_OK;
76 }
77 
Instance()78 nsHyphenationManager* nsHyphenationManager::Instance() {
79   if (sInstance == nullptr) {
80     sInstance = new nsHyphenationManager();
81 
82     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
83     if (obs) {
84       obs->AddObserver(sInstance, kMemoryPressureNotification, false);
85     }
86 
87     RegisterStrongMemoryReporter(new HyphenReporter());
88   }
89   return sInstance;
90 }
91 
Shutdown()92 void nsHyphenationManager::Shutdown() {
93   if (sInstance) {
94     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
95     if (obs) {
96       obs->RemoveObserver(sInstance, kMemoryPressureNotification);
97     }
98     delete sInstance;
99     sInstance = nullptr;
100   }
101 }
102 
nsHyphenationManager()103 nsHyphenationManager::nsHyphenationManager() {
104   LoadPatternList();
105   LoadAliases();
106 }
107 
~nsHyphenationManager()108 nsHyphenationManager::~nsHyphenationManager() { sInstance = nullptr; }
109 
GetHyphenator(nsAtom * aLocale)110 already_AddRefed<nsHyphenator> nsHyphenationManager::GetHyphenator(
111     nsAtom* aLocale) {
112   RefPtr<nsHyphenator> hyph;
113   mHyphenators.Get(aLocale, getter_AddRefs(hyph));
114   if (hyph) {
115     return hyph.forget();
116   }
117   nsCOMPtr<nsIURI> uri = mPatternFiles.Get(aLocale);
118   if (!uri) {
119     RefPtr<nsAtom> alias = mHyphAliases.Get(aLocale);
120     if (alias) {
121       mHyphenators.Get(alias, getter_AddRefs(hyph));
122       if (hyph) {
123         return hyph.forget();
124       }
125       uri = mPatternFiles.Get(alias);
126       if (uri) {
127         aLocale = alias;
128       }
129     }
130     if (!uri) {
131       // In the case of a locale such as "de-DE-1996", we try replacing
132       // successive trailing subtags with "-*" to find fallback patterns,
133       // so "de-DE-1996" -> "de-DE-*" (and then recursively -> "de-*")
134       nsAtomCString localeStr(aLocale);
135       if (StringEndsWith(localeStr, "-*"_ns)) {
136         localeStr.Truncate(localeStr.Length() - 2);
137       }
138       int32_t i = localeStr.RFindChar('-');
139       if (i > 1) {
140         localeStr.ReplaceLiteral(i, localeStr.Length() - i, "-*");
141         RefPtr<nsAtom> fuzzyLocale = NS_Atomize(localeStr);
142         return GetHyphenator(fuzzyLocale);
143       }
144       return nullptr;
145     }
146   }
147   nsAutoCString hyphCapPref("intl.hyphenate-capitalized.");
148   hyphCapPref.Append(nsAtomCString(aLocale));
149   hyph = new nsHyphenator(uri, Preferences::GetBool(hyphCapPref.get()));
150   if (hyph->IsValid()) {
151     mHyphenators.InsertOrUpdate(aLocale, RefPtr{hyph});
152     return hyph.forget();
153   }
154 #ifdef DEBUG
155   nsCString msg("failed to load patterns from ");
156   msg += uri->GetSpecOrDefault();
157   NS_WARNING(msg.get());
158 #endif
159   mPatternFiles.Remove(aLocale);
160   return nullptr;
161 }
162 
LoadPatternList()163 void nsHyphenationManager::LoadPatternList() {
164   mPatternFiles.Clear();
165   mHyphenators.Clear();
166 
167   LoadPatternListFromOmnijar(Omnijar::GRE);
168   LoadPatternListFromOmnijar(Omnijar::APP);
169 
170   nsCOMPtr<nsIProperties> dirSvc =
171       do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
172   if (!dirSvc) {
173     return;
174   }
175 
176   nsresult rv;
177   nsCOMPtr<nsIFile> greDir;
178   rv = dirSvc->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir));
179   if (NS_SUCCEEDED(rv)) {
180     greDir->AppendNative("hyphenation"_ns);
181     LoadPatternListFromDir(greDir);
182   }
183 
184   nsCOMPtr<nsIFile> appDir;
185   rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
186                    getter_AddRefs(appDir));
187   if (NS_SUCCEEDED(rv)) {
188     appDir->AppendNative("hyphenation"_ns);
189     bool equals;
190     if (NS_SUCCEEDED(appDir->Equals(greDir, &equals)) && !equals) {
191       LoadPatternListFromDir(appDir);
192     }
193   }
194 
195   nsCOMPtr<nsIFile> profileDir;
196   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
197                               getter_AddRefs(profileDir));
198   if (NS_SUCCEEDED(rv)) {
199     profileDir->AppendNative("hyphenation"_ns);
200     LoadPatternListFromDir(profileDir);
201   }
202 }
203 
204 // Extract the locale code we'll use to identify a given hyphenation resource
205 // from the path name as found in omnijar or on disk.
LocaleAtomFromPath(const nsCString & aPath)206 static already_AddRefed<nsAtom> LocaleAtomFromPath(const nsCString& aPath) {
207   MOZ_ASSERT(StringEndsWith(aPath, ".hyf"_ns) ||
208              StringEndsWith(aPath, ".dic"_ns));
209   nsCString locale(aPath);
210   locale.Truncate(locale.Length() - 4);      // strip ".hyf" or ".dic"
211   locale.Cut(0, locale.RFindChar('/') + 1);  // strip directory
212   ToLowerCase(locale);
213   if (StringBeginsWith(locale, "hyph_"_ns)) {
214     locale.Cut(0, 5);
215   }
216   for (uint32_t i = 0; i < locale.Length(); ++i) {
217     if (locale[i] == '_') {
218       locale.Replace(i, 1, '-');
219     }
220   }
221   return NS_Atomize(locale);
222 }
223 
LoadPatternListFromOmnijar(Omnijar::Type aType)224 void nsHyphenationManager::LoadPatternListFromOmnijar(Omnijar::Type aType) {
225   nsCString base;
226   nsresult rv = Omnijar::GetURIString(aType, base);
227   if (NS_FAILED(rv)) {
228     return;
229   }
230 
231   RefPtr<nsZipArchive> zip = Omnijar::GetReader(aType);
232   if (!zip) {
233     return;
234   }
235 
236   nsZipFind* find;
237   zip->FindInit("hyphenation/hyph_*.*", &find);
238   if (!find) {
239     return;
240   }
241 
242   const char* result;
243   uint16_t len;
244   while (NS_SUCCEEDED(find->FindNext(&result, &len))) {
245     nsCString uriString(base);
246     uriString.Append(result, len);
247     nsCOMPtr<nsIURI> uri;
248     rv = NS_NewURI(getter_AddRefs(uri), uriString);
249     if (NS_FAILED(rv)) {
250       continue;
251     }
252     nsCString locale;
253     rv = uri->GetPathQueryRef(locale);
254     if (NS_FAILED(rv)) {
255       continue;
256     }
257     RefPtr<nsAtom> localeAtom = LocaleAtomFromPath(locale);
258     mPatternFiles.InsertOrUpdate(localeAtom, uri);
259   }
260 
261   delete find;
262 }
263 
LoadPatternListFromDir(nsIFile * aDir)264 void nsHyphenationManager::LoadPatternListFromDir(nsIFile* aDir) {
265   nsresult rv;
266 
267   bool check = false;
268   rv = aDir->Exists(&check);
269   if (NS_FAILED(rv) || !check) {
270     return;
271   }
272 
273   rv = aDir->IsDirectory(&check);
274   if (NS_FAILED(rv) || !check) {
275     return;
276   }
277 
278   nsCOMPtr<nsIDirectoryEnumerator> files;
279   rv = aDir->GetDirectoryEntries(getter_AddRefs(files));
280   if (NS_FAILED(rv)) {
281     return;
282   }
283 
284   nsCOMPtr<nsIFile> file;
285   while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
286     nsAutoString dictName;
287     file->GetLeafName(dictName);
288     NS_ConvertUTF16toUTF8 path(dictName);
289     if (!(StringEndsWith(path, ".hyf"_ns) || StringEndsWith(path, ".dic"_ns))) {
290       continue;
291     }
292     RefPtr<nsAtom> localeAtom = LocaleAtomFromPath(path);
293     nsCOMPtr<nsIURI> uri;
294     nsresult rv = NS_NewFileURI(getter_AddRefs(uri), file);
295     if (NS_SUCCEEDED(rv)) {
296 #ifdef DEBUG_hyph
297       printf("adding hyphenation patterns for %s: %s\n",
298              nsAtomCString(localeAtom).get(), path.get());
299 #endif
300       mPatternFiles.InsertOrUpdate(localeAtom, uri);
301     }
302   }
303 }
304 
LoadAliases()305 void nsHyphenationManager::LoadAliases() {
306   nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch();
307   if (!prefRootBranch) {
308     return;
309   }
310   nsTArray<nsCString> prefNames;
311   nsresult rv =
312       prefRootBranch->GetChildList(kIntlHyphenationAliasPrefix, prefNames);
313   if (NS_SUCCEEDED(rv)) {
314     for (auto& prefName : prefNames) {
315       nsAutoCString value;
316       rv = Preferences::GetCString(prefName.get(), value);
317       if (NS_SUCCEEDED(rv)) {
318         nsAutoCString alias(prefName);
319         alias.Cut(0, sizeof(kIntlHyphenationAliasPrefix) - 1);
320         ToLowerCase(alias);
321         ToLowerCase(value);
322         RefPtr<nsAtom> aliasAtom = NS_Atomize(alias);
323         RefPtr<nsAtom> valueAtom = NS_Atomize(value);
324         mHyphAliases.InsertOrUpdate(aliasAtom, std::move(valueAtom));
325       }
326     }
327   }
328 }
329 
ShareHyphDictToProcess(nsIURI * aURI,base::ProcessId aPid,base::SharedMemoryHandle * aOutHandle,uint32_t * aOutSize)330 void nsHyphenationManager::ShareHyphDictToProcess(
331     nsIURI* aURI, base::ProcessId aPid, base::SharedMemoryHandle* aOutHandle,
332     uint32_t* aOutSize) {
333   MOZ_ASSERT(XRE_IsParentProcess());
334   // aURI will be referring to an omnijar resource (otherwise just bail).
335   *aOutHandle = base::SharedMemory::NULLHandle();
336   *aOutSize = 0;
337 
338   // Extract the locale code from the URI, and get the corresponding
339   // hyphenator (loading it into shared memory if necessary).
340   nsCString path;
341   nsCOMPtr<nsIJARURI> jar = do_QueryInterface(aURI);
342   if (jar) {
343     jar->GetJAREntry(path);
344   } else {
345     aURI->GetFilePath(path);
346   }
347 
348   RefPtr<nsAtom> localeAtom = LocaleAtomFromPath(path);
349   RefPtr<nsHyphenator> hyph = GetHyphenator(localeAtom);
350   if (!hyph) {
351     MOZ_ASSERT_UNREACHABLE("failed to find hyphenator");
352     return;
353   }
354 
355   hyph->CloneHandle(aOutHandle, aOutSize);
356 }
357 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)358 size_t nsHyphenationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
359   size_t result = aMallocSizeOf(this);
360 
361   result += mHyphAliases.ShallowSizeOfExcludingThis(aMallocSizeOf);
362 
363   result += mPatternFiles.ShallowSizeOfExcludingThis(aMallocSizeOf);
364   // Measurement of the URIs stored in mPatternFiles may be added later if DMD
365   // finds it is worthwhile.
366 
367   result += mHyphenators.ShallowSizeOfExcludingThis(aMallocSizeOf);
368 
369   return result;
370 }
371