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