nsStringBundle.cpp (28939B)
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 "nsStringBundle.h" 7 8 #include "nsID.h" 9 #include "nsString.h" 10 #include "nsIStringBundle.h" 11 #include "nsStringBundleService.h" 12 #include "nsArrayEnumerator.h" 13 #include "nscore.h" 14 #include "nsNetUtil.h" 15 #include "nsComponentManagerUtils.h" 16 #include "nsServiceManagerUtils.h" 17 #include "nsIChannel.h" 18 #include "nsIInputStream.h" 19 #include "nsIURI.h" 20 #include "nsIObserverService.h" 21 #include "nsCOMArray.h" 22 #include "nsTextFormatter.h" 23 #include "nsContentUtils.h" 24 #include "nsPersistentProperties.h" 25 #include "nsQueryObject.h" 26 #include "nsSimpleEnumerator.h" 27 #include "nsStringStream.h" 28 #include "mozilla/ipc/SharedMemoryHandle.h" 29 #include "mozilla/BinarySearch.h" 30 #include "mozilla/ClearOnShutdown.h" 31 #include "mozilla/URLPreloader.h" 32 #include "mozilla/Try.h" 33 #include "mozilla/dom/ContentParent.h" 34 #include "mozilla/dom/ipc/SharedStringMap.h" 35 36 // for async loading 37 #ifdef ASYNC_LOADING 38 # include "nsIBinaryInputStream.h" 39 # include "nsIStringStream.h" 40 #endif 41 42 using namespace mozilla; 43 44 using mozilla::dom::ContentParent; 45 using mozilla::dom::StringBundleDescriptor; 46 using mozilla::dom::ipc::SharedStringMap; 47 using mozilla::dom::ipc::SharedStringMapBuilder; 48 using mozilla::ipc::FileDescriptor; 49 50 /** 51 * A set of string bundle URLs which are loaded by content processes, and 52 * should be allocated in a shared memory region, and then sent to content 53 * processes. 54 * 55 * Note: This layout is chosen to avoid having to create a separate char* 56 * array pointing to the string constant values, which would require 57 * per-process relocations. The second array size is the length of the longest 58 * URL plus its null terminator. Shorter strings are null padded to this 59 * length. 60 * 61 * This should be kept in sync with the similar array in nsContentUtils.cpp, 62 * and updated with any other property files which need to be loaded in all 63 * content processes. 64 */ 65 static const char kContentBundles[][52] = { 66 "chrome://branding/locale/brand.properties", 67 "chrome://global/locale/commonDialogs.properties", 68 "chrome://global/locale/css.properties", 69 "chrome://global/locale/dom/dom.properties", 70 "chrome://global/locale/layout/HtmlForm.properties", 71 "chrome://global/locale/layout/htmlparser.properties", 72 "chrome://global/locale/layout_errors.properties", 73 "chrome://global/locale/mathml/mathml.properties", 74 "chrome://global/locale/printing.properties", 75 "chrome://global/locale/security/csp.properties", 76 "chrome://global/locale/security/security.properties", 77 "chrome://global/locale/svg/svg.properties", 78 "chrome://global/locale/xul.properties", 79 "chrome://necko/locale/necko.properties", 80 }; 81 82 static bool IsContentBundle(const nsCString& aUrl) { 83 size_t index; 84 return BinarySearchIf( 85 kContentBundles, 0, std::size(kContentBundles), 86 [&](const char* aElem) { 87 return Compare(aUrl, nsDependentCString(aElem)); 88 }, 89 &index); 90 } 91 92 namespace { 93 94 #define STRINGBUNDLEPROXY_IID \ 95 {0x537cf21b, 0x99fc, 0x4002, {0x9e, 0xec, 0x97, 0xbe, 0x4d, 0xe0, 0xb3, 0xdc}} 96 97 /** 98 * A simple proxy class for a string bundle instance which will be replaced by 99 * a different implementation later in the session. 100 * 101 * This is used when creating string bundles which should use shared memory, 102 * but the content process has not yet received their shared memory buffer. 103 * When the shared memory variant becomes available, this proxy is retarged to 104 * that instance, and the original non-shared instance is destroyed. 105 * 106 * At that point, the cache entry for the proxy is replaced with the shared 107 * memory instance, and callers which already have an instance of the proxy 108 * are redirected to the new instance. 109 */ 110 class StringBundleProxy : public nsIStringBundle { 111 NS_DECL_THREADSAFE_ISUPPORTS 112 113 NS_INLINE_DECL_STATIC_IID(STRINGBUNDLEPROXY_IID) 114 115 explicit StringBundleProxy(already_AddRefed<nsIStringBundle> aTarget) 116 : mMutex("StringBundleProxy::mMutex"), mTarget(aTarget) {} 117 118 void Retarget(nsIStringBundle* aTarget) { 119 MutexAutoLock automon(mMutex); 120 mTarget = aTarget; 121 } 122 123 // Forward nsIStringBundle methods (other than the `SizeOf*` methods) to 124 // `Target()`. 125 NS_IMETHOD GetStringFromID(int32_t aID, nsAString& _retval) override { 126 return Target()->GetStringFromID(aID, _retval); 127 } 128 129 NS_IMETHOD GetStringFromAUTF8Name(const nsACString& aName, 130 nsAString& _retval) override { 131 return Target()->GetStringFromAUTF8Name(aName, _retval); 132 } 133 NS_IMETHOD GetStringFromName(const char* aName, nsAString& _retval) override { 134 return Target()->GetStringFromName(aName, _retval); 135 } 136 137 NS_IMETHOD FormatStringFromAUTF8Name(const nsACString& aName, 138 const nsTArray<nsString>& params, 139 nsAString& _retval) override { 140 return Target()->FormatStringFromAUTF8Name(aName, params, _retval); 141 } 142 NS_IMETHOD FormatStringFromName(const char* aName, 143 const nsTArray<nsString>& params, 144 nsAString& _retval) override { 145 return Target()->FormatStringFromName(aName, params, _retval); 146 } 147 NS_IMETHOD GetSimpleEnumeration(nsISimpleEnumerator** _retval) override { 148 return Target()->GetSimpleEnumeration(_retval); 149 } 150 NS_IMETHOD AsyncPreload() override { return Target()->AsyncPreload(); } 151 152 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) override { 153 return aMallocSizeOf(this); 154 } 155 156 size_t SizeOfIncludingThisIfUnshared( 157 mozilla::MallocSizeOf aMallocSizeOf) override { 158 return mRefCnt == 1 ? SizeOfIncludingThis(aMallocSizeOf) : 0; 159 } 160 161 protected: 162 virtual ~StringBundleProxy() = default; 163 164 private: 165 Mutex mMutex MOZ_UNANNOTATED; 166 nsCOMPtr<nsIStringBundle> mTarget; 167 168 // Atomically reads mTarget and returns a strong reference to it. This 169 // allows for safe multi-threaded use when the proxy may be retargetted by 170 // the main thread during access. 171 nsCOMPtr<nsIStringBundle> Target() { 172 MutexAutoLock automon(mMutex); 173 return mTarget; 174 } 175 }; 176 177 NS_IMPL_ISUPPORTS(StringBundleProxy, nsIStringBundle, StringBundleProxy) 178 179 #define SHAREDSTRINGBUNDLE_IID \ 180 {0x7a8df5f7, 0x9e50, 0x44f6, {0xbf, 0x89, 0xc7, 0xad, 0x6c, 0x17, 0xf8, 0x5f}} 181 182 /** 183 * A string bundle backed by a read-only, shared memory buffer. This should 184 * only be used for string bundles which are used in child processes. 185 * 186 * Important: The memory allocated by these string bundles will never be freed 187 * before process shutdown, per the restrictions in SharedStringMap.h, so they 188 * should never be used for short-lived bundles. 189 */ 190 class SharedStringBundle final : public nsStringBundleBase { 191 public: 192 /** 193 * Initialize the string bundle with a file descriptor pointing to a 194 * pre-populated key-value store for this string bundle. This should only be 195 * called in child processes, for bundles initially created in the parent 196 * process. 197 */ 198 void SetMapFile(mozilla::ipc::ReadOnlySharedMemoryHandle&& aHandle); 199 200 NS_DECL_ISUPPORTS_INHERITED 201 NS_INLINE_DECL_STATIC_IID(SHAREDSTRINGBUNDLE_IID) 202 203 nsresult LoadProperties() override; 204 205 /** 206 * Returns a copy of the file descriptor pointing to the shared memory 207 * key-value store for this string bundle. This should only be called in the 208 * parent process, and may be used to send shared string bundles to child 209 * processes. 210 */ 211 mozilla::ipc::ReadOnlySharedMemoryHandle CloneHandle() const { 212 MOZ_ASSERT(XRE_IsParentProcess()); 213 if (mMapHandle.isSome()) { 214 return mMapHandle.ref().Clone(); 215 } 216 return mStringMap->CloneHandle(); 217 } 218 219 size_t MapSize() const { 220 if (mMapHandle.isSome()) { 221 return mMapHandle->Size(); 222 } 223 if (mStringMap) { 224 return mStringMap->MapSize(); 225 } 226 return 0; 227 } 228 229 bool Initialized() const { return mStringMap || mMapHandle.isSome(); } 230 231 StringBundleDescriptor GetDescriptor() const { 232 MOZ_ASSERT(Initialized()); 233 234 StringBundleDescriptor descriptor; 235 descriptor.bundleURL() = BundleURL(); 236 descriptor.mapHandle() = CloneHandle(); 237 return descriptor; 238 } 239 240 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) override; 241 242 static SharedStringBundle* Cast(nsIStringBundle* aStringBundle) { 243 return static_cast<SharedStringBundle*>(aStringBundle); 244 } 245 246 protected: 247 friend class nsStringBundleBase; 248 249 explicit SharedStringBundle(const char* aURLSpec) 250 : nsStringBundleBase(aURLSpec) {} 251 252 ~SharedStringBundle() override = default; 253 254 nsresult GetStringImpl(const nsACString& aName, nsAString& aResult) override; 255 256 nsresult GetSimpleEnumerationImpl(nsISimpleEnumerator** elements) override; 257 258 private: 259 RefPtr<SharedStringMap> mStringMap; 260 261 Maybe<mozilla::ipc::ReadOnlySharedMemoryHandle> mMapHandle; 262 }; 263 264 class StringMapEnumerator final : public nsSimpleEnumerator { 265 public: 266 NS_DECL_NSISIMPLEENUMERATOR 267 268 explicit StringMapEnumerator(SharedStringMap* aStringMap) 269 : mStringMap(aStringMap) {} 270 271 const nsID& DefaultInterface() override { 272 return NS_GET_IID(nsIPropertyElement); 273 } 274 275 protected: 276 virtual ~StringMapEnumerator() = default; 277 278 private: 279 RefPtr<SharedStringMap> mStringMap; 280 281 uint32_t mIndex = 0; 282 }; 283 284 template <typename T, typename... Args> 285 already_AddRefed<T> MakeBundle(Args... args) { 286 return nsStringBundleBase::Create<T>(args...); 287 } 288 289 template <typename T, typename... Args> 290 RefPtr<T> MakeBundleRefPtr(Args... args) { 291 return nsStringBundleBase::Create<T>(args...); 292 } 293 294 } // anonymous namespace 295 296 NS_IMPL_ISUPPORTS(nsStringBundleBase, nsIStringBundle, nsIMemoryReporter) 297 298 NS_IMPL_ISUPPORTS_INHERITED0(nsStringBundle, nsStringBundleBase) 299 NS_IMPL_ISUPPORTS_INHERITED(SharedStringBundle, nsStringBundleBase, 300 SharedStringBundle) 301 302 nsStringBundleBase::nsStringBundleBase(const char* aURLSpec) 303 : mPropertiesURL(aURLSpec), 304 mMutex("nsStringBundle.mMutex"), 305 mAttemptedLoad(false), 306 mLoaded(false) {} 307 308 nsStringBundleBase::~nsStringBundleBase() { 309 UnregisterWeakMemoryReporter(this); 310 } 311 312 void nsStringBundleBase::RegisterMemoryReporter() { 313 RegisterWeakMemoryReporter(this); 314 } 315 316 template <typename T, typename... Args> 317 /* static */ 318 already_AddRefed<T> nsStringBundleBase::Create(Args... args) { 319 RefPtr<T> bundle = new T(args...); 320 bundle->RegisterMemoryReporter(); 321 return bundle.forget(); 322 } 323 324 nsStringBundle::nsStringBundle(const char* aURLSpec) 325 : nsStringBundleBase(aURLSpec) {} 326 327 nsStringBundle::~nsStringBundle() = default; 328 329 NS_IMETHODIMP 330 nsStringBundleBase::AsyncPreload() { 331 return NS_DispatchToCurrentThreadQueue( 332 NewIdleRunnableMethod("nsStringBundleBase::LoadProperties", this, 333 &nsStringBundleBase::LoadProperties), 334 EventQueuePriority::Idle); 335 } 336 337 size_t nsStringBundle::SizeOfIncludingThis( 338 mozilla::MallocSizeOf aMallocSizeOf) { 339 size_t n = 0; 340 if (mProps) { 341 n += mProps->SizeOfIncludingThis(aMallocSizeOf); 342 } 343 return aMallocSizeOf(this) + n; 344 } 345 346 size_t nsStringBundleBase::SizeOfIncludingThis( 347 mozilla::MallocSizeOf aMallocSizeOf) { 348 return 0; 349 } 350 351 size_t nsStringBundleBase::SizeOfIncludingThisIfUnshared( 352 mozilla::MallocSizeOf aMallocSizeOf) { 353 if (mRefCnt == 1) { 354 return SizeOfIncludingThis(aMallocSizeOf); 355 } else { 356 return 0; 357 } 358 } 359 360 size_t SharedStringBundle::SizeOfIncludingThis( 361 mozilla::MallocSizeOf aMallocSizeOf) { 362 size_t n = 0; 363 if (mStringMap) { 364 n += aMallocSizeOf(mStringMap); 365 } 366 return aMallocSizeOf(this) + n; 367 } 368 369 NS_IMETHODIMP 370 nsStringBundleBase::CollectReports(nsIHandleReportCallback* aHandleReport, 371 nsISupports* aData, bool aAnonymize) { 372 // String bundle URLs are always local, and part of the distribution. 373 // There's no need to anonymize. 374 nsAutoCStringN<64> escapedURL(mPropertiesURL); 375 escapedURL.ReplaceChar('/', '\\'); 376 377 size_t sharedSize = 0; 378 size_t heapSize = SizeOfIncludingThis(MallocSizeOf); 379 380 nsAutoCStringN<256> path("explicit/string-bundles/"); 381 if (RefPtr<SharedStringBundle> shared = do_QueryObject(this)) { 382 path.AppendLiteral("SharedStringBundle"); 383 if (XRE_IsParentProcess()) { 384 sharedSize = shared->MapSize(); 385 } 386 } else { 387 path.AppendLiteral("nsStringBundle"); 388 } 389 390 path.AppendLiteral("(url=\""); 391 path.Append(escapedURL); 392 393 // Note: The memory reporter service holds a strong reference to reporters 394 // while collecting reports, so we want to ignore the extra ref in reports. 395 path.AppendLiteral("\", shared="); 396 path.AppendASCII(mRefCnt > 2 ? "true" : "false"); 397 path.AppendLiteral(", refCount="); 398 path.AppendInt(uint32_t(mRefCnt - 1)); 399 400 if (sharedSize) { 401 path.AppendLiteral(", sharedMemorySize="); 402 path.AppendInt(uint32_t(sharedSize)); 403 } 404 405 path.AppendLiteral(")"); 406 407 constexpr auto desc = 408 "A StringBundle instance representing the data in a (probably " 409 "localized) .properties file. Data may be shared between " 410 "processes."_ns; 411 412 aHandleReport->Callback(""_ns, path, KIND_HEAP, UNITS_BYTES, heapSize, desc, 413 aData); 414 415 if (sharedSize) { 416 path.ReplaceLiteral(0, sizeof("explicit/") - 1, "shared-"); 417 418 aHandleReport->Callback(""_ns, path, KIND_OTHER, UNITS_BYTES, sharedSize, 419 desc, aData); 420 } 421 422 return NS_OK; 423 } 424 425 nsresult nsStringBundleBase::ParseProperties(nsIPersistentProperties** aProps) { 426 // this is different than mLoaded, because we only want to attempt 427 // to load once 428 // we only want to load once, but if we've tried once and failed, 429 // continue to throw an error! 430 if (mAttemptedLoad) { 431 if (mLoaded) return NS_OK; 432 433 return NS_ERROR_UNEXPECTED; 434 } 435 436 MOZ_ASSERT(NS_IsMainThread(), 437 "String bundles must be initialized on the main thread " 438 "before they may be used off-main-thread"); 439 440 mAttemptedLoad = true; 441 442 nsresult rv; 443 444 // do it synchronously 445 nsCOMPtr<nsIURI> uri; 446 rv = NS_NewURI(getter_AddRefs(uri), mPropertiesURL); 447 if (NS_FAILED(rv)) return rv; 448 449 // whitelist check for local schemes 450 nsCString scheme; 451 uri->GetScheme(scheme); 452 if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("jar") && 453 !scheme.EqualsLiteral("resource") && !scheme.EqualsLiteral("file") && 454 !scheme.EqualsLiteral("data")) { 455 return NS_ERROR_ABORT; 456 } 457 458 nsCOMPtr<nsIInputStream> in; 459 460 auto result = URLPreloader::ReadURI(uri); 461 if (result.isOk()) { 462 MOZ_TRY(NS_NewCStringInputStream(getter_AddRefs(in), result.unwrap())); 463 } else { 464 nsCOMPtr<nsIChannel> channel; 465 rv = NS_NewChannel(getter_AddRefs(channel), uri, 466 nsContentUtils::GetSystemPrincipal(), 467 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 468 nsIContentPolicy::TYPE_OTHER); 469 470 if (NS_FAILED(rv)) return rv; 471 472 // It's a string bundle. We expect a text/plain type, so set that as hint 473 channel->SetContentType("text/plain"_ns); 474 475 rv = channel->Open(getter_AddRefs(in)); 476 if (NS_FAILED(rv)) return rv; 477 } 478 479 auto props = MakeRefPtr<nsPersistentProperties>(); 480 481 mAttemptedLoad = true; 482 483 MOZ_TRY(props->Load(in)); 484 props.forget(aProps); 485 486 mLoaded = true; 487 return NS_OK; 488 } 489 490 nsresult nsStringBundle::LoadProperties() { 491 // Something such as Necko might use string bundle after ClearOnShutdown is 492 // called. LocaleService etc is already down, so we cannot get bundle data. 493 if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) { 494 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 495 } 496 497 if (mProps) { 498 return NS_OK; 499 } 500 return ParseProperties(getter_AddRefs(mProps)); 501 } 502 503 nsresult SharedStringBundle::LoadProperties() { 504 if (mStringMap) return NS_OK; 505 506 if (mMapHandle.isSome()) { 507 mStringMap = new SharedStringMap(mMapHandle.extract()); 508 return NS_OK; 509 } 510 511 MOZ_ASSERT(NS_IsMainThread(), 512 "String bundles must be initialized on the main thread " 513 "before they may be used off-main-thread"); 514 515 // We can't access the locale service after shutdown has started, which 516 // means we can't attempt to load chrome: locale resources (which most of 517 // our string bundles come from). Since shared string bundles won't be 518 // useful after shutdown has started anyway (and we almost certainly got 519 // here from a pre-load attempt in an idle task), just bail out. 520 if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) { 521 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 522 } 523 524 // We should only populate shared memory string bundles in the parent 525 // process. Instances in the child process should always be instantiated 526 // with a shared memory file descriptor sent from the parent. 527 MOZ_ASSERT(XRE_IsParentProcess()); 528 529 nsCOMPtr<nsIPersistentProperties> props; 530 MOZ_TRY(ParseProperties(getter_AddRefs(props))); 531 532 SharedStringMapBuilder builder; 533 534 nsCOMPtr<nsISimpleEnumerator> iter; 535 MOZ_TRY(props->Enumerate(getter_AddRefs(iter))); 536 bool hasMore; 537 while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) { 538 nsCOMPtr<nsISupports> next; 539 MOZ_TRY(iter->GetNext(getter_AddRefs(next))); 540 541 nsresult rv; 542 nsCOMPtr<nsIPropertyElement> elem = do_QueryInterface(next, &rv); 543 MOZ_TRY(rv); 544 545 nsCString key; 546 nsString value; 547 MOZ_TRY(elem->GetKey(key)); 548 MOZ_TRY(elem->GetValue(value)); 549 550 builder.Add(key, value); 551 } 552 553 mStringMap = new SharedStringMap(std::move(builder)); 554 555 ContentParent::BroadcastStringBundle(GetDescriptor()); 556 557 return NS_OK; 558 } 559 560 void SharedStringBundle::SetMapFile( 561 mozilla::ipc::ReadOnlySharedMemoryHandle&& aHandle) { 562 MOZ_ASSERT(XRE_IsContentProcess()); 563 mStringMap = nullptr; 564 mMapHandle.emplace(std::move(aHandle)); 565 } 566 567 NS_IMETHODIMP 568 nsStringBundleBase::GetStringFromID(int32_t aID, nsAString& aResult) { 569 nsAutoCString idStr; 570 idStr.AppendInt(aID, 10); 571 return GetStringFromName(idStr.get(), aResult); 572 } 573 574 NS_IMETHODIMP 575 nsStringBundleBase::GetStringFromAUTF8Name(const nsACString& aName, 576 nsAString& aResult) { 577 return GetStringFromName(PromiseFlatCString(aName).get(), aResult); 578 } 579 580 NS_IMETHODIMP 581 nsStringBundleBase::GetStringFromName(const char* aName, nsAString& aResult) { 582 NS_ENSURE_ARG_POINTER(aName); 583 584 MutexAutoLock autolock(mMutex); 585 586 return GetStringImpl(nsDependentCString(aName), aResult); 587 } 588 589 nsresult nsStringBundle::GetStringImpl(const nsACString& aName, 590 nsAString& aResult) { 591 MOZ_TRY(LoadProperties()); 592 593 return mProps->GetStringProperty(aName, aResult); 594 } 595 596 nsresult SharedStringBundle::GetStringImpl(const nsACString& aName, 597 nsAString& aResult) { 598 MOZ_TRY(LoadProperties()); 599 600 if (mStringMap->Get(PromiseFlatCString(aName), aResult)) { 601 return NS_OK; 602 } 603 return NS_ERROR_FAILURE; 604 } 605 606 // this function supports at most 10 parameters.. see below for why 607 NS_IMETHODIMP 608 nsStringBundleBase::FormatStringFromAUTF8Name(const nsACString& aName, 609 const nsTArray<nsString>& aParams, 610 nsAString& aResult) { 611 return FormatStringFromName(PromiseFlatCString(aName).get(), aParams, 612 aResult); 613 } 614 615 // this function supports at most 10 parameters.. see below for why 616 NS_IMETHODIMP 617 nsStringBundleBase::FormatStringFromName(const char* aName, 618 const nsTArray<nsString>& aParams, 619 nsAString& aResult) { 620 NS_ASSERTION(!aParams.IsEmpty(), 621 "FormatStringFromName() without format parameters: use " 622 "GetStringFromName() instead"); 623 624 nsAutoString formatStr; 625 nsresult rv = GetStringFromName(aName, formatStr); 626 if (NS_FAILED(rv)) return rv; 627 628 return FormatString(formatStr.get(), aParams, aResult); 629 } 630 631 NS_IMETHODIMP 632 nsStringBundleBase::GetSimpleEnumeration(nsISimpleEnumerator** aElements) { 633 NS_ENSURE_ARG_POINTER(aElements); 634 635 return GetSimpleEnumerationImpl(aElements); 636 } 637 638 nsresult nsStringBundle::GetSimpleEnumerationImpl( 639 nsISimpleEnumerator** elements) { 640 MOZ_TRY(LoadProperties()); 641 642 return mProps->Enumerate(elements); 643 } 644 645 nsresult SharedStringBundle::GetSimpleEnumerationImpl( 646 nsISimpleEnumerator** aEnumerator) { 647 MOZ_TRY(LoadProperties()); 648 649 auto iter = MakeRefPtr<StringMapEnumerator>(mStringMap); 650 iter.forget(aEnumerator); 651 return NS_OK; 652 } 653 654 NS_IMETHODIMP 655 StringMapEnumerator::HasMoreElements(bool* aHasMore) { 656 *aHasMore = mIndex < mStringMap->Count(); 657 return NS_OK; 658 } 659 660 NS_IMETHODIMP 661 StringMapEnumerator::GetNext(nsISupports** aNext) { 662 if (mIndex >= mStringMap->Count()) { 663 return NS_ERROR_FAILURE; 664 } 665 666 auto elem = MakeRefPtr<nsPropertyElement>(mStringMap->GetKeyAt(mIndex), 667 mStringMap->GetValueAt(mIndex)); 668 669 elem.forget(aNext); 670 671 mIndex++; 672 return NS_OK; 673 } 674 675 nsresult nsStringBundleBase::FormatString(const char16_t* aFormatStr, 676 const nsTArray<nsString>& aParams, 677 nsAString& aResult) { 678 auto length = aParams.Length(); 679 NS_ENSURE_ARG(length <= 10); // enforce 10-parameter limit 680 681 // implementation note: you would think you could use vsmprintf 682 // to build up an arbitrary length array.. except that there 683 // is no way to build up a va_list at runtime! 684 // Don't believe me? See: 685 // http://www.eskimo.com/~scs/C-faq/q15.13.html 686 // -alecf 687 nsTextFormatter::ssprintf(aResult, aFormatStr, 688 length >= 1 ? aParams[0].get() : nullptr, 689 length >= 2 ? aParams[1].get() : nullptr, 690 length >= 3 ? aParams[2].get() : nullptr, 691 length >= 4 ? aParams[3].get() : nullptr, 692 length >= 5 ? aParams[4].get() : nullptr, 693 length >= 6 ? aParams[5].get() : nullptr, 694 length >= 7 ? aParams[6].get() : nullptr, 695 length >= 8 ? aParams[7].get() : nullptr, 696 length >= 9 ? aParams[8].get() : nullptr, 697 length >= 10 ? aParams[9].get() : nullptr); 698 699 return NS_OK; 700 } 701 702 ///////////////////////////////////////////////////////////////////////////////////////// 703 704 #define MAX_CACHED_BUNDLES 16 705 706 struct bundleCacheEntry_t final : public LinkedListElement<bundleCacheEntry_t> { 707 nsCString mHashKey; 708 nsCOMPtr<nsIStringBundle> mBundle; 709 710 MOZ_COUNTED_DEFAULT_CTOR(bundleCacheEntry_t) 711 712 MOZ_COUNTED_DTOR(bundleCacheEntry_t) 713 }; 714 715 nsStringBundleService::nsStringBundleService() 716 : mBundleMap(MAX_CACHED_BUNDLES) {} 717 718 NS_IMPL_ISUPPORTS(nsStringBundleService, nsIStringBundleService, nsIObserver, 719 nsISupportsWeakReference, nsIMemoryReporter) 720 721 nsStringBundleService::~nsStringBundleService() { 722 UnregisterWeakMemoryReporter(this); 723 flushBundleCache(/* ignoreShared = */ false); 724 } 725 726 nsresult nsStringBundleService::Init() { 727 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 728 if (os) { 729 os->AddObserver(this, "memory-pressure", true); 730 os->AddObserver(this, "profile-do-change", true); 731 os->AddObserver(this, "chrome-flush-caches", true); 732 os->AddObserver(this, "intl:app-locales-changed", true); 733 } 734 735 RegisterWeakMemoryReporter(this); 736 737 return NS_OK; 738 } 739 740 size_t nsStringBundleService::SizeOfIncludingThis( 741 mozilla::MallocSizeOf aMallocSizeOf) { 742 size_t n = mBundleMap.ShallowSizeOfExcludingThis(aMallocSizeOf); 743 for (const auto& data : mBundleMap.Values()) { 744 n += aMallocSizeOf(data); 745 n += data->mHashKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf); 746 } 747 return aMallocSizeOf(this) + n; 748 } 749 750 NS_IMETHODIMP 751 nsStringBundleService::Observe(nsISupports* aSubject, const char* aTopic, 752 const char16_t* aSomeData) { 753 if (strcmp("profile-do-change", aTopic) == 0 || 754 strcmp("chrome-flush-caches", aTopic) == 0 || 755 strcmp("intl:app-locales-changed", aTopic) == 0) { 756 flushBundleCache(/* ignoreShared = */ false); 757 mBundleMap.Clear(); 758 } else if (strcmp("memory-pressure", aTopic) == 0) { 759 flushBundleCache(/* ignoreShared = */ true); 760 } 761 762 return NS_OK; 763 } 764 765 void nsStringBundleService::flushBundleCache(bool ignoreShared) { 766 LinkedList<bundleCacheEntry_t> newList; 767 768 while (!mBundleCache.isEmpty()) { 769 UniquePtr<bundleCacheEntry_t> entry(mBundleCache.popFirst()); 770 auto* bundle = nsStringBundleBase::Cast(entry->mBundle); 771 772 if (ignoreShared && bundle->IsShared()) { 773 newList.insertBack(entry.release()); 774 } else { 775 mBundleMap.Remove(entry->mHashKey); 776 } 777 } 778 779 mBundleCache = std::move(newList); 780 } 781 782 NS_IMETHODIMP 783 nsStringBundleService::FlushBundles() { 784 flushBundleCache(/* ignoreShared = */ false); 785 return NS_OK; 786 } 787 788 void nsStringBundleService::SendContentBundles(ContentParent* aContentParent) { 789 nsTArray<StringBundleDescriptor> bundles; 790 791 for (auto* entry : mSharedBundles) { 792 auto bundle = SharedStringBundle::Cast(entry->mBundle); 793 794 if (bundle->Initialized()) { 795 bundles.AppendElement(bundle->GetDescriptor()); 796 } 797 } 798 799 (void)aContentParent->SendRegisterStringBundles(std::move(bundles)); 800 } 801 802 void nsStringBundleService::RegisterContentBundle( 803 const nsACString& aBundleURL, 804 mozilla::ipc::ReadOnlySharedMemoryHandle&& aMapHandle) { 805 RefPtr<StringBundleProxy> proxy; 806 807 bundleCacheEntry_t* cacheEntry = mBundleMap.Get(aBundleURL); 808 if (cacheEntry) { 809 if (RefPtr<SharedStringBundle> shared = 810 do_QueryObject(cacheEntry->mBundle)) { 811 return; 812 } 813 814 proxy = do_QueryObject(cacheEntry->mBundle); 815 MOZ_ASSERT(proxy); 816 cacheEntry->remove(); 817 delete cacheEntry; 818 } 819 820 auto bundle = MakeBundleRefPtr<SharedStringBundle>( 821 PromiseFlatCString(aBundleURL).get()); 822 bundle->SetMapFile(std::move(aMapHandle)); 823 824 if (proxy) { 825 proxy->Retarget(bundle); 826 } 827 828 cacheEntry = insertIntoCache(bundle.forget(), aBundleURL); 829 mSharedBundles.insertBack(cacheEntry); 830 } 831 832 NS_IMETHODIMP 833 nsStringBundleService::CreateBundle(const char* aURLSpec, 834 nsIStringBundle** aResult) { 835 nsDependentCString key(aURLSpec); 836 bundleCacheEntry_t* cacheEntry = mBundleMap.Get(key); 837 838 RefPtr<SharedStringBundle> shared; 839 840 if (cacheEntry) { 841 // Remove the entry from the list so it can be re-inserted at the back. 842 cacheEntry->remove(); 843 844 shared = do_QueryObject(cacheEntry->mBundle); 845 } else { 846 nsCOMPtr<nsIStringBundle> bundle; 847 bool isContent = IsContentBundle(key); 848 if (!isContent || !XRE_IsParentProcess()) { 849 bundle = MakeBundle<nsStringBundle>(aURLSpec); 850 } 851 852 // If this is a bundle which is used by the content processes, we want to 853 // load it into a shared memory region. 854 // 855 // If we're in the parent process, just create a new SharedStringBundle, 856 // and populate it from the properties file. 857 // 858 // If we're in a child process, the fact that the bundle is not already in 859 // the cache means that we haven't received its shared memory descriptor 860 // from the parent yet. There's not much we can do about that besides 861 // wait, but we need to return a bundle now. So instead of a shared memory 862 // bundle, we create a temporary proxy, which points to a non-shared 863 // bundle initially, and is retarged to a shared memory bundle when it 864 // becomes available. 865 if (isContent) { 866 if (XRE_IsParentProcess()) { 867 shared = MakeBundle<SharedStringBundle>(aURLSpec); 868 bundle = shared; 869 } else { 870 bundle = new StringBundleProxy(bundle.forget()); 871 } 872 } 873 874 cacheEntry = insertIntoCache(bundle.forget(), key); 875 } 876 877 if (shared) { 878 mSharedBundles.insertBack(cacheEntry); 879 } else { 880 mBundleCache.insertBack(cacheEntry); 881 } 882 883 // finally, return the value 884 *aResult = cacheEntry->mBundle; 885 NS_ADDREF(*aResult); 886 887 return NS_OK; 888 } 889 890 UniquePtr<bundleCacheEntry_t> nsStringBundleService::evictOneEntry() { 891 for (auto* entry : mBundleCache) { 892 auto* bundle = nsStringBundleBase::Cast(entry->mBundle); 893 if (!bundle->IsShared()) { 894 entry->remove(); 895 mBundleMap.Remove(entry->mHashKey); 896 return UniquePtr<bundleCacheEntry_t>(entry); 897 } 898 } 899 return nullptr; 900 } 901 902 bundleCacheEntry_t* nsStringBundleService::insertIntoCache( 903 already_AddRefed<nsIStringBundle> aBundle, const nsACString& aHashKey) { 904 UniquePtr<bundleCacheEntry_t> cacheEntry; 905 906 if (mBundleMap.Count() >= MAX_CACHED_BUNDLES) { 907 cacheEntry = evictOneEntry(); 908 } 909 910 if (!cacheEntry) { 911 cacheEntry.reset(new bundleCacheEntry_t()); 912 } 913 914 cacheEntry->mHashKey = aHashKey; 915 cacheEntry->mBundle = aBundle; 916 917 mBundleMap.InsertOrUpdate(cacheEntry->mHashKey, cacheEntry.get()); 918 919 return cacheEntry.release(); 920 }