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