Coverage Report

Created: 2018-09-25 14:53

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