tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }