/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 | } |