tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FluentBundle.cpp (16646B)


      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/. */
      6 
      7 #include "FluentBundle.h"
      8 #include "nsContentUtils.h"
      9 #include "mozilla/dom/ToJSValue.h"
     10 #include "mozilla/dom/UnionTypes.h"
     11 #include "mozilla/intl/NumberFormat.h"
     12 #include "mozilla/intl/DateTimeFormat.h"
     13 #include "mozilla/intl/DateTimePatternGenerator.h"
     14 #include "nsIInputStream.h"
     15 #include "nsStringFwd.h"
     16 #include "nsTArray.h"
     17 #include "js/PropertyAndElement.h"  // JS_DefineElement
     18 
     19 using namespace mozilla::dom;
     20 
     21 namespace mozilla {
     22 namespace intl {
     23 
     24 class SizeableUTF8Buffer {
     25 public:
     26  using CharType = char;
     27 
     28  bool reserve(size_t size) {
     29    mBuffer.reset(reinterpret_cast<CharType*>(malloc(size)));
     30    mCapacity = size;
     31    return true;
     32  }
     33 
     34  CharType* data() { return mBuffer.get(); }
     35 
     36  size_t capacity() const { return mCapacity; }
     37 
     38  void written(size_t amount) { mWritten = amount; }
     39 
     40  size_t mWritten = 0;
     41  size_t mCapacity = 0;
     42 
     43  struct FreePolicy {
     44    void operator()(const void* ptr) { free(const_cast<void*>(ptr)); }
     45  };
     46 
     47  UniquePtr<CharType[], FreePolicy> mBuffer;
     48 };
     49 
     50 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentPattern, mParent)
     51 
     52 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId)
     53    : mId(aId), mParent(aParent) {
     54  MOZ_COUNT_CTOR(FluentPattern);
     55 }
     56 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId,
     57                             const nsACString& aAttrName)
     58    : mId(aId), mAttrName(aAttrName), mParent(aParent) {
     59  MOZ_COUNT_CTOR(FluentPattern);
     60 }
     61 
     62 JSObject* FluentPattern::WrapObject(JSContext* aCx,
     63                                    JS::Handle<JSObject*> aGivenProto) {
     64  return FluentPattern_Binding::Wrap(aCx, this, aGivenProto);
     65 }
     66 
     67 FluentPattern::~FluentPattern() { MOZ_COUNT_DTOR(FluentPattern); };
     68 
     69 /* FluentBundle */
     70 
     71 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle, mParent)
     72 
     73 FluentBundle::FluentBundle(nsISupports* aParent,
     74                           UniquePtr<ffi::FluentBundleRc> aRaw)
     75    : mParent(aParent), mRaw(std::move(aRaw)) {
     76  MOZ_COUNT_CTOR(FluentBundle);
     77 }
     78 
     79 already_AddRefed<FluentBundle> FluentBundle::Constructor(
     80    const dom::GlobalObject& aGlobal,
     81    const UTF8StringOrUTF8StringSequence& aLocales,
     82    const dom::FluentBundleOptions& aOptions, ErrorResult& aRv) {
     83  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
     84  if (!global) {
     85    aRv.Throw(NS_ERROR_FAILURE);
     86    return nullptr;
     87  }
     88 
     89  bool useIsolating = aOptions.mUseIsolating;
     90 
     91  nsAutoCString pseudoStrategy;
     92  if (aOptions.mPseudoStrategy.WasPassed()) {
     93    pseudoStrategy = aOptions.mPseudoStrategy.Value();
     94  }
     95 
     96  UniquePtr<ffi::FluentBundleRc> raw;
     97 
     98  if (aLocales.IsUTF8String()) {
     99    const nsACString& locale = aLocales.GetAsUTF8String();
    100    raw.reset(
    101        ffi::fluent_bundle_new_single(&locale, useIsolating, &pseudoStrategy));
    102  } else {
    103    const auto& locales = aLocales.GetAsUTF8StringSequence();
    104    raw.reset(ffi::fluent_bundle_new(locales.Elements(), locales.Length(),
    105                                     useIsolating, &pseudoStrategy));
    106  }
    107 
    108  if (!raw) {
    109    aRv.ThrowInvalidStateError(
    110        "Failed to create the FluentBundle. Check the "
    111        "locales and pseudo strategy arguments.");
    112    return nullptr;
    113  }
    114 
    115  return do_AddRef(new FluentBundle(global, std::move(raw)));
    116 }
    117 
    118 JSObject* FluentBundle::WrapObject(JSContext* aCx,
    119                                   JS::Handle<JSObject*> aGivenProto) {
    120  return FluentBundle_Binding::Wrap(aCx, this, aGivenProto);
    121 }
    122 
    123 FluentBundle::~FluentBundle() { MOZ_COUNT_DTOR(FluentBundle); };
    124 
    125 void FluentBundle::GetLocales(nsTArray<nsCString>& aLocales) {
    126  fluent_bundle_get_locales(mRaw.get(), &aLocales);
    127 }
    128 
    129 void FluentBundle::AddResource(
    130    FluentResource& aResource,
    131    const dom::FluentBundleAddResourceOptions& aOptions) {
    132  bool allowOverrides = aOptions.mAllowOverrides;
    133  nsTArray<nsCString> errors;
    134 
    135  fluent_bundle_add_resource(mRaw.get(), aResource.Raw(), allowOverrides,
    136                             &errors);
    137 
    138  for (auto& err : errors) {
    139    nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err), "L10n"_ns,
    140                                          false, true,
    141                                          nsIScriptError::warningFlag);
    142  }
    143 }
    144 
    145 bool FluentBundle::HasMessage(const nsACString& aId) {
    146  return fluent_bundle_has_message(mRaw.get(), &aId);
    147 }
    148 
    149 void FluentBundle::GetMessage(const nsACString& aId,
    150                              Nullable<FluentMessage>& aRetVal) {
    151  bool hasValue = false;
    152  nsTArray<nsCString> attributes;
    153  bool exists =
    154      fluent_bundle_get_message(mRaw.get(), &aId, &hasValue, &attributes);
    155  if (exists) {
    156    FluentMessage& msg = aRetVal.SetValue();
    157    if (hasValue) {
    158      msg.mValue = new FluentPattern(mParent, aId);
    159    }
    160    for (auto& name : attributes) {
    161      auto newEntry = msg.mAttributes.Entries().AppendElement(fallible);
    162      newEntry->mKey = name;
    163      newEntry->mValue = new FluentPattern(mParent, aId, name);
    164    }
    165  }
    166 }
    167 
    168 bool extendJSArrayWithErrors(JSContext* aCx, JS::Handle<JSObject*> aErrors,
    169                             nsTArray<nsCString>& aInput) {
    170  uint32_t length;
    171  if (NS_WARN_IF(!JS::GetArrayLength(aCx, aErrors, &length))) {
    172    return false;
    173  }
    174 
    175  for (auto& err : aInput) {
    176    JS::Rooted<JS::Value> jsval(aCx);
    177    if (!ToJSValue(aCx, NS_ConvertUTF8toUTF16(err), &jsval)) {
    178      return false;
    179    }
    180    if (!JS_DefineElement(aCx, aErrors, length++, jsval, JSPROP_ENUMERATE)) {
    181      return false;
    182    }
    183  }
    184  return true;
    185 }
    186 
    187 /* static */
    188 void FluentBundle::ConvertArgs(const L10nArgs& aArgs,
    189                               nsTArray<ffi::L10nArg>& aRetVal) {
    190  aRetVal.SetCapacity(aArgs.Entries().Length());
    191  for (const auto& entry : aArgs.Entries()) {
    192    if (!entry.mValue.IsNull()) {
    193      const auto& value = entry.mValue.Value();
    194 
    195      if (value.IsUTF8String()) {
    196        aRetVal.AppendElement(ffi::L10nArg{
    197            &entry.mKey,
    198            ffi::FluentArgument::String(&value.GetAsUTF8String())});
    199      } else {
    200        aRetVal.AppendElement(ffi::L10nArg{
    201            &entry.mKey, ffi::FluentArgument::Double_(value.GetAsDouble())});
    202      }
    203    }
    204  }
    205 }
    206 
    207 void FluentBundle::FormatPattern(JSContext* aCx, const FluentPattern& aPattern,
    208                                 const Nullable<L10nArgs>& aArgs,
    209                                 const Optional<JS::Handle<JSObject*>>& aErrors,
    210                                 nsACString& aRetVal, ErrorResult& aRv) {
    211  nsTArray<ffi::L10nArg> l10nArgs;
    212 
    213  if (!aArgs.IsNull()) {
    214    const L10nArgs& args = aArgs.Value();
    215    ConvertArgs(args, l10nArgs);
    216  }
    217 
    218  nsTArray<nsCString> errors;
    219  bool succeeded = fluent_bundle_format_pattern(mRaw.get(), &aPattern.mId,
    220                                                &aPattern.mAttrName, &l10nArgs,
    221                                                &aRetVal, &errors);
    222 
    223  if (!succeeded) {
    224    return aRv.ThrowInvalidStateError(
    225        "Failed to format the FluentPattern. Likely the "
    226        "pattern could not be retrieved from the bundle.");
    227  }
    228 
    229  if (aErrors.WasPassed()) {
    230    if (!extendJSArrayWithErrors(aCx, aErrors.Value(), errors)) {
    231      aRv.ThrowUnknownError("Failed to add errors to an error array.");
    232    }
    233  }
    234 }
    235 
    236 // FFI
    237 
    238 extern "C" {
    239 ffi::RawNumberFormatter* FluentBuiltInNumberFormatterCreate(
    240    const nsCString* aLocale, const ffi::FluentNumberOptionsRaw* aOptions) {
    241  NumberFormatOptions options;
    242  switch (aOptions->style) {
    243    case ffi::FluentNumberStyleRaw::Decimal:
    244      break;
    245    case ffi::FluentNumberStyleRaw::Currency: {
    246      std::string currency = aOptions->currency.get();
    247      switch (aOptions->currency_display) {
    248        case ffi::FluentNumberCurrencyDisplayStyleRaw::Symbol:
    249          options.mCurrency = Some(std::make_pair(
    250              currency, NumberFormatOptions::CurrencyDisplay::Symbol));
    251          break;
    252        case ffi::FluentNumberCurrencyDisplayStyleRaw::Code:
    253          options.mCurrency = Some(std::make_pair(
    254              currency, NumberFormatOptions::CurrencyDisplay::Code));
    255          break;
    256        case ffi::FluentNumberCurrencyDisplayStyleRaw::Name:
    257          options.mCurrency = Some(std::make_pair(
    258              currency, NumberFormatOptions::CurrencyDisplay::Name));
    259          break;
    260        default:
    261          MOZ_ASSERT_UNREACHABLE();
    262          break;
    263      }
    264    } break;
    265    case ffi::FluentNumberStyleRaw::Percent:
    266      options.mPercent = true;
    267      break;
    268    default:
    269      MOZ_ASSERT_UNREACHABLE();
    270      break;
    271  }
    272 
    273  options.mGrouping = aOptions->use_grouping
    274                          ? NumberFormatOptions::Grouping::Auto
    275                          : NumberFormatOptions::Grouping::Never;
    276  options.mMinIntegerDigits = Some(aOptions->minimum_integer_digits);
    277 
    278  if (aOptions->minimum_significant_digits >= 0 ||
    279      aOptions->maximum_significant_digits >= 0) {
    280    options.mSignificantDigits =
    281        Some(std::make_pair(aOptions->minimum_significant_digits,
    282                            aOptions->maximum_significant_digits));
    283  } else {
    284    options.mFractionDigits = Some(std::make_pair(
    285        aOptions->minimum_fraction_digits, aOptions->maximum_fraction_digits));
    286  }
    287 
    288  Result<UniquePtr<NumberFormat>, ICUError> result =
    289      NumberFormat::TryCreate(aLocale->get(), options);
    290 
    291  MOZ_ASSERT(result.isOk());
    292 
    293  if (result.isOk()) {
    294    return reinterpret_cast<ffi::RawNumberFormatter*>(
    295        result.unwrap().release());
    296  }
    297 
    298  return nullptr;
    299 }
    300 
    301 uint8_t* FluentBuiltInNumberFormatterFormat(
    302    const ffi::RawNumberFormatter* aFormatter, double input, size_t* aOutCount,
    303    size_t* aOutCapacity) {
    304  const NumberFormat* nf = reinterpret_cast<const NumberFormat*>(aFormatter);
    305 
    306  SizeableUTF8Buffer buffer;
    307  if (nf->format(input, buffer).isOk()) {
    308    *aOutCount = buffer.mWritten;
    309    *aOutCapacity = buffer.mCapacity;
    310    return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
    311  }
    312 
    313  return nullptr;
    314 }
    315 
    316 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter* aFormatter) {
    317  delete reinterpret_cast<NumberFormat*>(aFormatter);
    318 }
    319 
    320 /* DateTime */
    321 
    322 static Maybe<DateTimeFormat::Style> GetStyle(ffi::FluentDateTimeStyle aStyle) {
    323  switch (aStyle) {
    324    case ffi::FluentDateTimeStyle::Full:
    325      return Some(DateTimeFormat::Style::Full);
    326    case ffi::FluentDateTimeStyle::Long:
    327      return Some(DateTimeFormat::Style::Long);
    328    case ffi::FluentDateTimeStyle::Medium:
    329      return Some(DateTimeFormat::Style::Medium);
    330    case ffi::FluentDateTimeStyle::Short:
    331      return Some(DateTimeFormat::Style::Short);
    332    case ffi::FluentDateTimeStyle::None:
    333      return Nothing();
    334  }
    335  MOZ_ASSERT_UNREACHABLE();
    336  return Nothing();
    337 }
    338 
    339 static Maybe<DateTimeFormat::Text> GetText(
    340    ffi::FluentDateTimeTextComponent aText) {
    341  switch (aText) {
    342    case ffi::FluentDateTimeTextComponent::Long:
    343      return Some(DateTimeFormat::Text::Long);
    344    case ffi::FluentDateTimeTextComponent::Short:
    345      return Some(DateTimeFormat::Text::Short);
    346    case ffi::FluentDateTimeTextComponent::Narrow:
    347      return Some(DateTimeFormat::Text::Narrow);
    348    case ffi::FluentDateTimeTextComponent::None:
    349      return Nothing();
    350  }
    351  MOZ_ASSERT_UNREACHABLE();
    352  return Nothing();
    353 }
    354 
    355 static Maybe<DateTimeFormat::Month> GetMonth(
    356    ffi::FluentDateTimeMonthComponent aMonth) {
    357  switch (aMonth) {
    358    case ffi::FluentDateTimeMonthComponent::Numeric:
    359      return Some(DateTimeFormat::Month::Numeric);
    360    case ffi::FluentDateTimeMonthComponent::TwoDigit:
    361      return Some(DateTimeFormat::Month::TwoDigit);
    362    case ffi::FluentDateTimeMonthComponent::Long:
    363      return Some(DateTimeFormat::Month::Long);
    364    case ffi::FluentDateTimeMonthComponent::Short:
    365      return Some(DateTimeFormat::Month::Short);
    366    case ffi::FluentDateTimeMonthComponent::Narrow:
    367      return Some(DateTimeFormat::Month::Narrow);
    368    case ffi::FluentDateTimeMonthComponent::None:
    369      return Nothing();
    370  }
    371  MOZ_ASSERT_UNREACHABLE();
    372  return Nothing();
    373 }
    374 
    375 static Maybe<DateTimeFormat::Numeric> GetNumeric(
    376    ffi::FluentDateTimeNumericComponent aNumeric) {
    377  switch (aNumeric) {
    378    case ffi::FluentDateTimeNumericComponent::Numeric:
    379      return Some(DateTimeFormat::Numeric::Numeric);
    380    case ffi::FluentDateTimeNumericComponent::TwoDigit:
    381      return Some(DateTimeFormat::Numeric::TwoDigit);
    382    case ffi::FluentDateTimeNumericComponent::None:
    383      return Nothing();
    384  }
    385  MOZ_ASSERT_UNREACHABLE();
    386  return Nothing();
    387 }
    388 
    389 static Maybe<DateTimeFormat::TimeZoneName> GetTimeZoneName(
    390    ffi::FluentDateTimeTimeZoneNameComponent aTimeZoneName) {
    391  switch (aTimeZoneName) {
    392    case ffi::FluentDateTimeTimeZoneNameComponent::Long:
    393      return Some(DateTimeFormat::TimeZoneName::Long);
    394    case ffi::FluentDateTimeTimeZoneNameComponent::Short:
    395      return Some(DateTimeFormat::TimeZoneName::Short);
    396    case ffi::FluentDateTimeTimeZoneNameComponent::None:
    397      return Nothing();
    398  }
    399  MOZ_ASSERT_UNREACHABLE();
    400  return Nothing();
    401 }
    402 
    403 static Maybe<DateTimeFormat::HourCycle> GetHourCycle(
    404    ffi::FluentDateTimeHourCycle aHourCycle) {
    405  switch (aHourCycle) {
    406    case ffi::FluentDateTimeHourCycle::H24:
    407      return Some(DateTimeFormat::HourCycle::H24);
    408    case ffi::FluentDateTimeHourCycle::H23:
    409      return Some(DateTimeFormat::HourCycle::H23);
    410    case ffi::FluentDateTimeHourCycle::H12:
    411      return Some(DateTimeFormat::HourCycle::H12);
    412    case ffi::FluentDateTimeHourCycle::H11:
    413      return Some(DateTimeFormat::HourCycle::H11);
    414    case ffi::FluentDateTimeHourCycle::None:
    415      return Nothing();
    416  }
    417  MOZ_ASSERT_UNREACHABLE();
    418  return Nothing();
    419 }
    420 
    421 static Maybe<DateTimeFormat::ComponentsBag> GetComponentsBag(
    422    ffi::FluentDateTimeOptions aOptions) {
    423  if (GetStyle(aOptions.date_style) || GetStyle(aOptions.time_style)) {
    424    return Nothing();
    425  }
    426 
    427  DateTimeFormat::ComponentsBag components;
    428  components.era = GetText(aOptions.era);
    429  components.year = GetNumeric(aOptions.year);
    430  components.month = GetMonth(aOptions.month);
    431  components.day = GetNumeric(aOptions.day);
    432  components.weekday = GetText(aOptions.weekday);
    433  components.hour = GetNumeric(aOptions.hour);
    434  components.minute = GetNumeric(aOptions.minute);
    435  components.second = GetNumeric(aOptions.second);
    436  components.timeZoneName = GetTimeZoneName(aOptions.time_zone_name);
    437  components.hourCycle = GetHourCycle(aOptions.hour_cycle);
    438 
    439  if (!components.era && !components.year && !components.month &&
    440      !components.day && !components.weekday && !components.hour &&
    441      !components.minute && !components.second && !components.timeZoneName) {
    442    return Nothing();
    443  }
    444 
    445  return Some(components);
    446 }
    447 
    448 ffi::RawDateTimeFormatter* FluentBuiltInDateTimeFormatterCreate(
    449    const nsCString* aLocale, ffi::FluentDateTimeOptions aOptions) {
    450  auto genResult = DateTimePatternGenerator::TryCreate(aLocale->get());
    451  if (genResult.isErr()) {
    452    MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
    453    return nullptr;
    454  }
    455  UniquePtr<DateTimePatternGenerator> dateTimePatternGenerator =
    456      genResult.unwrap();
    457 
    458  if (auto components = GetComponentsBag(aOptions)) {
    459    auto result = DateTimeFormat::TryCreateFromComponents(
    460        Span(*aLocale), *components, dateTimePatternGenerator.get());
    461    if (result.isErr()) {
    462      MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
    463      return nullptr;
    464    }
    465 
    466    return reinterpret_cast<ffi::RawDateTimeFormatter*>(
    467        result.unwrap().release());
    468  }
    469 
    470  DateTimeFormat::StyleBag style;
    471  style.date = GetStyle(aOptions.date_style);
    472  style.time = GetStyle(aOptions.time_style);
    473 
    474  auto result = DateTimeFormat::TryCreateFromStyle(
    475      Span(*aLocale), style, dateTimePatternGenerator.get());
    476 
    477  if (result.isErr()) {
    478    MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
    479    return nullptr;
    480  }
    481 
    482  return reinterpret_cast<ffi::RawDateTimeFormatter*>(
    483      result.unwrap().release());
    484 }
    485 
    486 uint8_t* FluentBuiltInDateTimeFormatterFormat(
    487    const ffi::RawDateTimeFormatter* aFormatter, double aUnixEpoch,
    488    uint32_t* aOutCount) {
    489  const auto* dtFormat = reinterpret_cast<const DateTimeFormat*>(aFormatter);
    490 
    491  SizeableUTF8Buffer buffer;
    492  dtFormat->TryFormat(aUnixEpoch, buffer).unwrap();
    493 
    494  *aOutCount = buffer.mWritten;
    495 
    496  return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
    497 }
    498 
    499 void FluentBuiltInDateTimeFormatterDestroy(
    500    ffi::RawDateTimeFormatter* aFormatter) {
    501  delete reinterpret_cast<const DateTimeFormat*>(aFormatter);
    502 }
    503 }
    504 
    505 }  // namespace intl
    506 }  // namespace mozilla