Bug 1758984 - add status to mozlog crash so group summary is not marked as OK. r...
[gecko.git] / intl / l10n / FluentBundle.cpp
blobae8182e2095255fda2e1f3ff982eef17ec36fe57
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "FluentBundle.h"
8 #include "nsContentUtils.h"
9 #include "mozilla/dom/UnionTypes.h"
10 #include "mozilla/intl/NumberFormat.h"
11 #include "mozilla/intl/DateTimeFormat.h"
12 #include "mozilla/intl/DateTimePatternGenerator.h"
13 #include "nsIInputStream.h"
14 #include "nsStringFwd.h"
15 #include "nsTArray.h"
16 #include "js/PropertyAndElement.h" // JS_DefineElement
18 using namespace mozilla::dom;
20 namespace mozilla {
21 namespace intl {
23 class SizeableUTF8Buffer {
24 public:
25 using CharType = char;
27 bool reserve(size_t size) {
28 mBuffer.reset(reinterpret_cast<CharType*>(malloc(size)));
29 mCapacity = size;
30 return true;
33 CharType* data() { return mBuffer.get(); }
35 size_t capacity() const { return mCapacity; }
37 void written(size_t amount) { mWritten = amount; }
39 size_t mWritten = 0;
40 size_t mCapacity = 0;
42 struct FreePolicy {
43 void operator()(const void* ptr) { free(const_cast<void*>(ptr)); }
46 UniquePtr<CharType[], FreePolicy> mBuffer;
49 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentPattern, mParent)
51 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentPattern, AddRef)
52 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentPattern, Release)
54 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId)
55 : mId(aId), mParent(aParent) {
56 MOZ_COUNT_CTOR(FluentPattern);
58 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId,
59 const nsACString& aAttrName)
60 : mId(aId), mAttrName(aAttrName), mParent(aParent) {
61 MOZ_COUNT_CTOR(FluentPattern);
64 JSObject* FluentPattern::WrapObject(JSContext* aCx,
65 JS::Handle<JSObject*> aGivenProto) {
66 return FluentPattern_Binding::Wrap(aCx, this, aGivenProto);
69 FluentPattern::~FluentPattern() { MOZ_COUNT_DTOR(FluentPattern); };
71 /* FluentBundle */
73 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle, mParent)
75 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentBundle, AddRef)
76 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentBundle, Release)
78 FluentBundle::FluentBundle(nsISupports* aParent,
79 UniquePtr<ffi::FluentBundleRc> aRaw)
80 : mParent(aParent), mRaw(std::move(aRaw)) {
81 MOZ_COUNT_CTOR(FluentBundle);
84 already_AddRefed<FluentBundle> FluentBundle::Constructor(
85 const dom::GlobalObject& aGlobal,
86 const UTF8StringOrUTF8StringSequence& aLocales,
87 const dom::FluentBundleOptions& aOptions, ErrorResult& aRv) {
88 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
89 if (!global) {
90 aRv.Throw(NS_ERROR_FAILURE);
91 return nullptr;
94 bool useIsolating = aOptions.mUseIsolating;
96 nsAutoCString pseudoStrategy;
97 if (aOptions.mPseudoStrategy.WasPassed()) {
98 pseudoStrategy = aOptions.mPseudoStrategy.Value();
101 UniquePtr<ffi::FluentBundleRc> raw;
103 if (aLocales.IsUTF8String()) {
104 const nsACString& locale = aLocales.GetAsUTF8String();
105 raw.reset(
106 ffi::fluent_bundle_new_single(&locale, useIsolating, &pseudoStrategy));
107 } else {
108 const auto& locales = aLocales.GetAsUTF8StringSequence();
109 raw.reset(ffi::fluent_bundle_new(locales.Elements(), locales.Length(),
110 useIsolating, &pseudoStrategy));
113 if (!raw) {
114 aRv.ThrowInvalidStateError(
115 "Failed to create the FluentBundle. Check the "
116 "locales and pseudo strategy arguments.");
117 return nullptr;
120 return do_AddRef(new FluentBundle(global, std::move(raw)));
123 JSObject* FluentBundle::WrapObject(JSContext* aCx,
124 JS::Handle<JSObject*> aGivenProto) {
125 return FluentBundle_Binding::Wrap(aCx, this, aGivenProto);
128 FluentBundle::~FluentBundle() { MOZ_COUNT_DTOR(FluentBundle); };
130 void FluentBundle::GetLocales(nsTArray<nsCString>& aLocales) {
131 fluent_bundle_get_locales(mRaw.get(), &aLocales);
134 void FluentBundle::AddResource(
135 FluentResource& aResource,
136 const dom::FluentBundleAddResourceOptions& aOptions) {
137 bool allowOverrides = aOptions.mAllowOverrides;
138 nsTArray<nsCString> errors;
140 fluent_bundle_add_resource(mRaw.get(), aResource.Raw(), allowOverrides,
141 &errors);
143 for (auto& err : errors) {
144 nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err), "L10n",
145 false, true,
146 nsIScriptError::warningFlag);
150 bool FluentBundle::HasMessage(const nsACString& aId) {
151 return fluent_bundle_has_message(mRaw.get(), &aId);
154 void FluentBundle::GetMessage(const nsACString& aId,
155 Nullable<FluentMessage>& aRetVal) {
156 bool hasValue = false;
157 nsTArray<nsCString> attributes;
158 bool exists =
159 fluent_bundle_get_message(mRaw.get(), &aId, &hasValue, &attributes);
160 if (exists) {
161 FluentMessage& msg = aRetVal.SetValue();
162 if (hasValue) {
163 msg.mValue = new FluentPattern(mParent, aId);
165 for (auto& name : attributes) {
166 auto newEntry = msg.mAttributes.Entries().AppendElement(fallible);
167 newEntry->mKey = name;
168 newEntry->mValue = new FluentPattern(mParent, aId, name);
173 bool extendJSArrayWithErrors(JSContext* aCx, JS::Handle<JSObject*> aErrors,
174 nsTArray<nsCString>& aInput) {
175 uint32_t length;
176 if (NS_WARN_IF(!JS::GetArrayLength(aCx, aErrors, &length))) {
177 return false;
180 for (auto& err : aInput) {
181 JS::Rooted<JS::Value> jsval(aCx);
182 if (!ToJSValue(aCx, NS_ConvertUTF8toUTF16(err), &jsval)) {
183 return false;
185 if (!JS_DefineElement(aCx, aErrors, length++, jsval, JSPROP_ENUMERATE)) {
186 return false;
189 return true;
192 /* static */
193 void FluentBundle::ConvertArgs(const L10nArgs& aArgs,
194 nsTArray<ffi::L10nArg>& aRetVal) {
195 aRetVal.SetCapacity(aArgs.Entries().Length());
196 for (const auto& entry : aArgs.Entries()) {
197 if (!entry.mValue.IsNull()) {
198 const auto& value = entry.mValue.Value();
200 if (value.IsUTF8String()) {
201 aRetVal.AppendElement(ffi::L10nArg{
202 &entry.mKey,
203 ffi::FluentArgument::String(&value.GetAsUTF8String())});
204 } else {
205 aRetVal.AppendElement(ffi::L10nArg{
206 &entry.mKey, ffi::FluentArgument::Double_(value.GetAsDouble())});
212 void FluentBundle::FormatPattern(JSContext* aCx, const FluentPattern& aPattern,
213 const Nullable<L10nArgs>& aArgs,
214 const Optional<JS::Handle<JSObject*>>& aErrors,
215 nsACString& aRetVal, ErrorResult& aRv) {
216 nsTArray<ffi::L10nArg> l10nArgs;
218 if (!aArgs.IsNull()) {
219 const L10nArgs& args = aArgs.Value();
220 ConvertArgs(args, l10nArgs);
223 nsTArray<nsCString> errors;
224 bool succeeded = fluent_bundle_format_pattern(mRaw.get(), &aPattern.mId,
225 &aPattern.mAttrName, &l10nArgs,
226 &aRetVal, &errors);
228 if (!succeeded) {
229 return aRv.ThrowInvalidStateError(
230 "Failed to format the FluentPattern. Likely the "
231 "pattern could not be retrieved from the bundle.");
234 if (aErrors.WasPassed()) {
235 if (!extendJSArrayWithErrors(aCx, aErrors.Value(), errors)) {
236 aRv.ThrowUnknownError("Failed to add errors to an error array.");
241 // FFI
243 extern "C" {
244 ffi::RawNumberFormatter* FluentBuiltInNumberFormatterCreate(
245 const nsCString* aLocale, const ffi::FluentNumberOptionsRaw* aOptions) {
246 NumberFormatOptions options;
247 switch (aOptions->style) {
248 case ffi::FluentNumberStyleRaw::Decimal:
249 break;
250 case ffi::FluentNumberStyleRaw::Currency: {
251 std::string currency = aOptions->currency.get();
252 switch (aOptions->currency_display) {
253 case ffi::FluentNumberCurrencyDisplayStyleRaw::Symbol:
254 options.mCurrency = Some(std::make_pair(
255 currency, NumberFormatOptions::CurrencyDisplay::Symbol));
256 break;
257 case ffi::FluentNumberCurrencyDisplayStyleRaw::Code:
258 options.mCurrency = Some(std::make_pair(
259 currency, NumberFormatOptions::CurrencyDisplay::Code));
260 break;
261 case ffi::FluentNumberCurrencyDisplayStyleRaw::Name:
262 options.mCurrency = Some(std::make_pair(
263 currency, NumberFormatOptions::CurrencyDisplay::Name));
264 break;
265 default:
266 MOZ_ASSERT_UNREACHABLE();
267 break;
269 } break;
270 case ffi::FluentNumberStyleRaw::Percent:
271 options.mPercent = true;
272 break;
273 default:
274 MOZ_ASSERT_UNREACHABLE();
275 break;
278 options.mGrouping = aOptions->use_grouping
279 ? NumberFormatOptions::Grouping::Auto
280 : NumberFormatOptions::Grouping::Never;
281 options.mMinIntegerDigits = Some(aOptions->minimum_integer_digits);
283 if (aOptions->minimum_significant_digits >= 0 ||
284 aOptions->maximum_significant_digits >= 0) {
285 options.mSignificantDigits =
286 Some(std::make_pair(aOptions->minimum_significant_digits,
287 aOptions->maximum_significant_digits));
288 } else {
289 options.mFractionDigits = Some(std::make_pair(
290 aOptions->minimum_fraction_digits, aOptions->maximum_fraction_digits));
293 Result<UniquePtr<NumberFormat>, ICUError> result =
294 NumberFormat::TryCreate(aLocale->get(), options);
296 MOZ_ASSERT(result.isOk());
298 if (result.isOk()) {
299 return reinterpret_cast<ffi::RawNumberFormatter*>(
300 result.unwrap().release());
303 return nullptr;
306 uint8_t* FluentBuiltInNumberFormatterFormat(
307 const ffi::RawNumberFormatter* aFormatter, double input, size_t* aOutCount,
308 size_t* aOutCapacity) {
309 const NumberFormat* nf = reinterpret_cast<const NumberFormat*>(aFormatter);
311 SizeableUTF8Buffer buffer;
312 if (nf->format(input, buffer).isOk()) {
313 *aOutCount = buffer.mWritten;
314 *aOutCapacity = buffer.mCapacity;
315 return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
318 return nullptr;
321 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter* aFormatter) {
322 delete reinterpret_cast<NumberFormat*>(aFormatter);
325 /* DateTime */
327 static Maybe<DateTimeFormat::Style> GetStyle(ffi::FluentDateTimeStyle aStyle) {
328 switch (aStyle) {
329 case ffi::FluentDateTimeStyle::Full:
330 return Some(DateTimeFormat::Style::Full);
331 case ffi::FluentDateTimeStyle::Long:
332 return Some(DateTimeFormat::Style::Long);
333 case ffi::FluentDateTimeStyle::Medium:
334 return Some(DateTimeFormat::Style::Medium);
335 case ffi::FluentDateTimeStyle::Short:
336 return Some(DateTimeFormat::Style::Short);
337 case ffi::FluentDateTimeStyle::None:
338 return Nothing();
340 MOZ_ASSERT_UNREACHABLE();
341 return Nothing();
344 ffi::RawDateTimeFormatter* FluentBuiltInDateTimeFormatterCreate(
345 const nsCString* aLocale, const ffi::FluentDateTimeOptionsRaw* aOptions) {
346 auto genResult = DateTimePatternGenerator::TryCreate(aLocale->get());
347 if (genResult.isErr()) {
348 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
349 return nullptr;
351 UniquePtr<DateTimePatternGenerator> dateTimePatternGenerator =
352 genResult.unwrap();
354 if (aOptions->date_style == ffi::FluentDateTimeStyle::None &&
355 aOptions->time_style == ffi::FluentDateTimeStyle::None &&
356 !aOptions->skeleton.IsEmpty()) {
357 auto result = DateTimeFormat::TryCreateFromSkeleton(
358 Span(aLocale->get(), aLocale->Length()),
359 Span(aOptions->skeleton.get(), aOptions->skeleton.Length()),
360 dateTimePatternGenerator.get(), Nothing());
361 if (result.isErr()) {
362 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
363 return nullptr;
366 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
367 result.unwrap().release());
370 DateTimeFormat::StyleBag style;
371 style.date = GetStyle(aOptions->date_style);
372 style.time = GetStyle(aOptions->time_style);
374 auto result = DateTimeFormat::TryCreateFromStyle(
375 Span(aLocale->get(), aLocale->Length()), style,
376 dateTimePatternGenerator.get());
378 if (result.isErr()) {
379 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
380 return nullptr;
383 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
384 result.unwrap().release());
387 uint8_t* FluentBuiltInDateTimeFormatterFormat(
388 const ffi::RawDateTimeFormatter* aFormatter, double aUnixEpoch,
389 uint32_t* aOutCount) {
390 const auto* dtFormat = reinterpret_cast<const DateTimeFormat*>(aFormatter);
392 SizeableUTF8Buffer buffer;
393 dtFormat->TryFormat(aUnixEpoch, buffer).unwrap();
395 *aOutCount = buffer.mWritten;
397 return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
400 void FluentBuiltInDateTimeFormatterDestroy(
401 ffi::RawDateTimeFormatter* aFormatter) {
402 delete reinterpret_cast<const DateTimeFormat*>(aFormatter);
406 } // namespace intl
407 } // namespace mozilla