Source code

Revision control

Other Tools

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