Bug 1808234 - Clean up xpcshell variant scheduling. r=jmaher
[gecko.git] / intl / l10n / FluentBundle.cpp
blobe2bdada5b52105015e4bcf52daa6236ae4e5a917
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 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId)
52 : mId(aId), mParent(aParent) {
53 MOZ_COUNT_CTOR(FluentPattern);
55 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId,
56 const nsACString& aAttrName)
57 : mId(aId), mAttrName(aAttrName), mParent(aParent) {
58 MOZ_COUNT_CTOR(FluentPattern);
61 JSObject* FluentPattern::WrapObject(JSContext* aCx,
62 JS::Handle<JSObject*> aGivenProto) {
63 return FluentPattern_Binding::Wrap(aCx, this, aGivenProto);
66 FluentPattern::~FluentPattern() { MOZ_COUNT_DTOR(FluentPattern); };
68 /* FluentBundle */
70 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle, mParent)
72 FluentBundle::FluentBundle(nsISupports* aParent,
73 UniquePtr<ffi::FluentBundleRc> aRaw)
74 : mParent(aParent), mRaw(std::move(aRaw)) {
75 MOZ_COUNT_CTOR(FluentBundle);
78 already_AddRefed<FluentBundle> FluentBundle::Constructor(
79 const dom::GlobalObject& aGlobal,
80 const UTF8StringOrUTF8StringSequence& aLocales,
81 const dom::FluentBundleOptions& aOptions, ErrorResult& aRv) {
82 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
83 if (!global) {
84 aRv.Throw(NS_ERROR_FAILURE);
85 return nullptr;
88 bool useIsolating = aOptions.mUseIsolating;
90 nsAutoCString pseudoStrategy;
91 if (aOptions.mPseudoStrategy.WasPassed()) {
92 pseudoStrategy = aOptions.mPseudoStrategy.Value();
95 UniquePtr<ffi::FluentBundleRc> raw;
97 if (aLocales.IsUTF8String()) {
98 const nsACString& locale = aLocales.GetAsUTF8String();
99 raw.reset(
100 ffi::fluent_bundle_new_single(&locale, useIsolating, &pseudoStrategy));
101 } else {
102 const auto& locales = aLocales.GetAsUTF8StringSequence();
103 raw.reset(ffi::fluent_bundle_new(locales.Elements(), locales.Length(),
104 useIsolating, &pseudoStrategy));
107 if (!raw) {
108 aRv.ThrowInvalidStateError(
109 "Failed to create the FluentBundle. Check the "
110 "locales and pseudo strategy arguments.");
111 return nullptr;
114 return do_AddRef(new FluentBundle(global, std::move(raw)));
117 JSObject* FluentBundle::WrapObject(JSContext* aCx,
118 JS::Handle<JSObject*> aGivenProto) {
119 return FluentBundle_Binding::Wrap(aCx, this, aGivenProto);
122 FluentBundle::~FluentBundle() { MOZ_COUNT_DTOR(FluentBundle); };
124 void FluentBundle::GetLocales(nsTArray<nsCString>& aLocales) {
125 fluent_bundle_get_locales(mRaw.get(), &aLocales);
128 void FluentBundle::AddResource(
129 FluentResource& aResource,
130 const dom::FluentBundleAddResourceOptions& aOptions) {
131 bool allowOverrides = aOptions.mAllowOverrides;
132 nsTArray<nsCString> errors;
134 fluent_bundle_add_resource(mRaw.get(), aResource.Raw(), allowOverrides,
135 &errors);
137 for (auto& err : errors) {
138 nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err), "L10n"_ns,
139 false, true,
140 nsIScriptError::warningFlag);
144 bool FluentBundle::HasMessage(const nsACString& aId) {
145 return fluent_bundle_has_message(mRaw.get(), &aId);
148 void FluentBundle::GetMessage(const nsACString& aId,
149 Nullable<FluentMessage>& aRetVal) {
150 bool hasValue = false;
151 nsTArray<nsCString> attributes;
152 bool exists =
153 fluent_bundle_get_message(mRaw.get(), &aId, &hasValue, &attributes);
154 if (exists) {
155 FluentMessage& msg = aRetVal.SetValue();
156 if (hasValue) {
157 msg.mValue = new FluentPattern(mParent, aId);
159 for (auto& name : attributes) {
160 auto newEntry = msg.mAttributes.Entries().AppendElement(fallible);
161 newEntry->mKey = name;
162 newEntry->mValue = new FluentPattern(mParent, aId, name);
167 bool extendJSArrayWithErrors(JSContext* aCx, JS::Handle<JSObject*> aErrors,
168 nsTArray<nsCString>& aInput) {
169 uint32_t length;
170 if (NS_WARN_IF(!JS::GetArrayLength(aCx, aErrors, &length))) {
171 return false;
174 for (auto& err : aInput) {
175 JS::Rooted<JS::Value> jsval(aCx);
176 if (!ToJSValue(aCx, NS_ConvertUTF8toUTF16(err), &jsval)) {
177 return false;
179 if (!JS_DefineElement(aCx, aErrors, length++, jsval, JSPROP_ENUMERATE)) {
180 return false;
183 return true;
186 /* static */
187 void FluentBundle::ConvertArgs(const L10nArgs& aArgs,
188 nsTArray<ffi::L10nArg>& aRetVal) {
189 aRetVal.SetCapacity(aArgs.Entries().Length());
190 for (const auto& entry : aArgs.Entries()) {
191 if (!entry.mValue.IsNull()) {
192 const auto& value = entry.mValue.Value();
194 if (value.IsUTF8String()) {
195 aRetVal.AppendElement(ffi::L10nArg{
196 &entry.mKey,
197 ffi::FluentArgument::String(&value.GetAsUTF8String())});
198 } else {
199 aRetVal.AppendElement(ffi::L10nArg{
200 &entry.mKey, ffi::FluentArgument::Double_(value.GetAsDouble())});
206 void FluentBundle::FormatPattern(JSContext* aCx, const FluentPattern& aPattern,
207 const Nullable<L10nArgs>& aArgs,
208 const Optional<JS::Handle<JSObject*>>& aErrors,
209 nsACString& aRetVal, ErrorResult& aRv) {
210 nsTArray<ffi::L10nArg> l10nArgs;
212 if (!aArgs.IsNull()) {
213 const L10nArgs& args = aArgs.Value();
214 ConvertArgs(args, l10nArgs);
217 nsTArray<nsCString> errors;
218 bool succeeded = fluent_bundle_format_pattern(mRaw.get(), &aPattern.mId,
219 &aPattern.mAttrName, &l10nArgs,
220 &aRetVal, &errors);
222 if (!succeeded) {
223 return aRv.ThrowInvalidStateError(
224 "Failed to format the FluentPattern. Likely the "
225 "pattern could not be retrieved from the bundle.");
228 if (aErrors.WasPassed()) {
229 if (!extendJSArrayWithErrors(aCx, aErrors.Value(), errors)) {
230 aRv.ThrowUnknownError("Failed to add errors to an error array.");
235 // FFI
237 extern "C" {
238 ffi::RawNumberFormatter* FluentBuiltInNumberFormatterCreate(
239 const nsCString* aLocale, const ffi::FluentNumberOptionsRaw* aOptions) {
240 NumberFormatOptions options;
241 switch (aOptions->style) {
242 case ffi::FluentNumberStyleRaw::Decimal:
243 break;
244 case ffi::FluentNumberStyleRaw::Currency: {
245 std::string currency = aOptions->currency.get();
246 switch (aOptions->currency_display) {
247 case ffi::FluentNumberCurrencyDisplayStyleRaw::Symbol:
248 options.mCurrency = Some(std::make_pair(
249 currency, NumberFormatOptions::CurrencyDisplay::Symbol));
250 break;
251 case ffi::FluentNumberCurrencyDisplayStyleRaw::Code:
252 options.mCurrency = Some(std::make_pair(
253 currency, NumberFormatOptions::CurrencyDisplay::Code));
254 break;
255 case ffi::FluentNumberCurrencyDisplayStyleRaw::Name:
256 options.mCurrency = Some(std::make_pair(
257 currency, NumberFormatOptions::CurrencyDisplay::Name));
258 break;
259 default:
260 MOZ_ASSERT_UNREACHABLE();
261 break;
263 } break;
264 case ffi::FluentNumberStyleRaw::Percent:
265 options.mPercent = true;
266 break;
267 default:
268 MOZ_ASSERT_UNREACHABLE();
269 break;
272 options.mGrouping = aOptions->use_grouping
273 ? NumberFormatOptions::Grouping::Auto
274 : NumberFormatOptions::Grouping::Never;
275 options.mMinIntegerDigits = Some(aOptions->minimum_integer_digits);
277 if (aOptions->minimum_significant_digits >= 0 ||
278 aOptions->maximum_significant_digits >= 0) {
279 options.mSignificantDigits =
280 Some(std::make_pair(aOptions->minimum_significant_digits,
281 aOptions->maximum_significant_digits));
282 } else {
283 options.mFractionDigits = Some(std::make_pair(
284 aOptions->minimum_fraction_digits, aOptions->maximum_fraction_digits));
287 Result<UniquePtr<NumberFormat>, ICUError> result =
288 NumberFormat::TryCreate(aLocale->get(), options);
290 MOZ_ASSERT(result.isOk());
292 if (result.isOk()) {
293 return reinterpret_cast<ffi::RawNumberFormatter*>(
294 result.unwrap().release());
297 return nullptr;
300 uint8_t* FluentBuiltInNumberFormatterFormat(
301 const ffi::RawNumberFormatter* aFormatter, double input, size_t* aOutCount,
302 size_t* aOutCapacity) {
303 const NumberFormat* nf = reinterpret_cast<const NumberFormat*>(aFormatter);
305 SizeableUTF8Buffer buffer;
306 if (nf->format(input, buffer).isOk()) {
307 *aOutCount = buffer.mWritten;
308 *aOutCapacity = buffer.mCapacity;
309 return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
312 return nullptr;
315 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter* aFormatter) {
316 delete reinterpret_cast<NumberFormat*>(aFormatter);
319 /* DateTime */
321 static Maybe<DateTimeFormat::Style> GetStyle(ffi::FluentDateTimeStyle aStyle) {
322 switch (aStyle) {
323 case ffi::FluentDateTimeStyle::Full:
324 return Some(DateTimeFormat::Style::Full);
325 case ffi::FluentDateTimeStyle::Long:
326 return Some(DateTimeFormat::Style::Long);
327 case ffi::FluentDateTimeStyle::Medium:
328 return Some(DateTimeFormat::Style::Medium);
329 case ffi::FluentDateTimeStyle::Short:
330 return Some(DateTimeFormat::Style::Short);
331 case ffi::FluentDateTimeStyle::None:
332 return Nothing();
334 MOZ_ASSERT_UNREACHABLE();
335 return Nothing();
338 static Maybe<DateTimeFormat::Text> GetText(
339 ffi::FluentDateTimeTextComponent aText) {
340 switch (aText) {
341 case ffi::FluentDateTimeTextComponent::Long:
342 return Some(DateTimeFormat::Text::Long);
343 case ffi::FluentDateTimeTextComponent::Short:
344 return Some(DateTimeFormat::Text::Short);
345 case ffi::FluentDateTimeTextComponent::Narrow:
346 return Some(DateTimeFormat::Text::Narrow);
347 case ffi::FluentDateTimeTextComponent::None:
348 return Nothing();
350 MOZ_ASSERT_UNREACHABLE();
351 return Nothing();
354 static Maybe<DateTimeFormat::Month> GetMonth(
355 ffi::FluentDateTimeMonthComponent aMonth) {
356 switch (aMonth) {
357 case ffi::FluentDateTimeMonthComponent::Numeric:
358 return Some(DateTimeFormat::Month::Numeric);
359 case ffi::FluentDateTimeMonthComponent::TwoDigit:
360 return Some(DateTimeFormat::Month::TwoDigit);
361 case ffi::FluentDateTimeMonthComponent::Long:
362 return Some(DateTimeFormat::Month::Long);
363 case ffi::FluentDateTimeMonthComponent::Short:
364 return Some(DateTimeFormat::Month::Short);
365 case ffi::FluentDateTimeMonthComponent::Narrow:
366 return Some(DateTimeFormat::Month::Narrow);
367 case ffi::FluentDateTimeMonthComponent::None:
368 return Nothing();
370 MOZ_ASSERT_UNREACHABLE();
371 return Nothing();
374 static Maybe<DateTimeFormat::Numeric> GetNumeric(
375 ffi::FluentDateTimeNumericComponent aNumeric) {
376 switch (aNumeric) {
377 case ffi::FluentDateTimeNumericComponent::Numeric:
378 return Some(DateTimeFormat::Numeric::Numeric);
379 case ffi::FluentDateTimeNumericComponent::TwoDigit:
380 return Some(DateTimeFormat::Numeric::TwoDigit);
381 case ffi::FluentDateTimeNumericComponent::None:
382 return Nothing();
384 MOZ_ASSERT_UNREACHABLE();
385 return Nothing();
388 static Maybe<DateTimeFormat::TimeZoneName> GetTimeZoneName(
389 ffi::FluentDateTimeTimeZoneNameComponent aTimeZoneName) {
390 switch (aTimeZoneName) {
391 case ffi::FluentDateTimeTimeZoneNameComponent::Long:
392 return Some(DateTimeFormat::TimeZoneName::Long);
393 case ffi::FluentDateTimeTimeZoneNameComponent::Short:
394 return Some(DateTimeFormat::TimeZoneName::Short);
395 case ffi::FluentDateTimeTimeZoneNameComponent::None:
396 return Nothing();
398 MOZ_ASSERT_UNREACHABLE();
399 return Nothing();
402 static Maybe<DateTimeFormat::HourCycle> GetHourCycle(
403 ffi::FluentDateTimeHourCycle aHourCycle) {
404 switch (aHourCycle) {
405 case ffi::FluentDateTimeHourCycle::H24:
406 return Some(DateTimeFormat::HourCycle::H24);
407 case ffi::FluentDateTimeHourCycle::H23:
408 return Some(DateTimeFormat::HourCycle::H23);
409 case ffi::FluentDateTimeHourCycle::H12:
410 return Some(DateTimeFormat::HourCycle::H12);
411 case ffi::FluentDateTimeHourCycle::H11:
412 return Some(DateTimeFormat::HourCycle::H11);
413 case ffi::FluentDateTimeHourCycle::None:
414 return Nothing();
416 MOZ_ASSERT_UNREACHABLE();
417 return Nothing();
420 static Maybe<DateTimeFormat::ComponentsBag> GetComponentsBag(
421 ffi::FluentDateTimeOptions aOptions) {
422 if (GetStyle(aOptions.date_style) || GetStyle(aOptions.time_style)) {
423 return Nothing();
426 DateTimeFormat::ComponentsBag components;
427 components.era = GetText(aOptions.era);
428 components.year = GetNumeric(aOptions.year);
429 components.month = GetMonth(aOptions.month);
430 components.day = GetNumeric(aOptions.day);
431 components.weekday = GetText(aOptions.weekday);
432 components.hour = GetNumeric(aOptions.hour);
433 components.minute = GetNumeric(aOptions.minute);
434 components.second = GetNumeric(aOptions.second);
435 components.timeZoneName = GetTimeZoneName(aOptions.time_zone_name);
436 components.hourCycle = GetHourCycle(aOptions.hour_cycle);
438 if (!components.era && !components.year && !components.month &&
439 !components.day && !components.weekday && !components.hour &&
440 !components.minute && !components.second && !components.timeZoneName) {
441 return Nothing();
444 return Some(components);
447 ffi::RawDateTimeFormatter* FluentBuiltInDateTimeFormatterCreate(
448 const nsCString* aLocale, ffi::FluentDateTimeOptions aOptions) {
449 auto genResult = DateTimePatternGenerator::TryCreate(aLocale->get());
450 if (genResult.isErr()) {
451 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
452 return nullptr;
454 UniquePtr<DateTimePatternGenerator> dateTimePatternGenerator =
455 genResult.unwrap();
457 if (auto components = GetComponentsBag(aOptions)) {
458 auto result = DateTimeFormat::TryCreateFromComponents(
459 Span(*aLocale), *components, dateTimePatternGenerator.get());
460 if (result.isErr()) {
461 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
462 return nullptr;
465 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
466 result.unwrap().release());
469 DateTimeFormat::StyleBag style;
470 style.date = GetStyle(aOptions.date_style);
471 style.time = GetStyle(aOptions.time_style);
473 auto result = DateTimeFormat::TryCreateFromStyle(
474 Span(*aLocale), style, dateTimePatternGenerator.get());
476 if (result.isErr()) {
477 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
478 return nullptr;
481 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
482 result.unwrap().release());
485 uint8_t* FluentBuiltInDateTimeFormatterFormat(
486 const ffi::RawDateTimeFormatter* aFormatter, double aUnixEpoch,
487 uint32_t* aOutCount) {
488 const auto* dtFormat = reinterpret_cast<const DateTimeFormat*>(aFormatter);
490 SizeableUTF8Buffer buffer;
491 dtFormat->TryFormat(aUnixEpoch, buffer).unwrap();
493 *aOutCount = buffer.mWritten;
495 return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
498 void FluentBuiltInDateTimeFormatterDestroy(
499 ffi::RawDateTimeFormatter* aFormatter) {
500 delete reinterpret_cast<const DateTimeFormat*>(aFormatter);
504 } // namespace intl
505 } // namespace mozilla