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/UnionTypes.h"
10 #include "mozilla/intl/NumberFormat.h"
11 #include "mozilla/intl/DateTimeFormat.h"
12 #include "nsIInputStream.h"
13 #include "nsStringFwd.h"
14 #include "nsTArray.h"
15
16 using namespace mozilla::dom;
17
18 namespace mozilla {
19 namespace intl {
20
21 class SizeableUTF8Buffer {
22 public:
23 using CharType = uint8_t;
24
reserve(size_t size)25 bool reserve(size_t size) {
26 mBuffer.reset(reinterpret_cast<CharType*>(malloc(size)));
27 mCapacity = size;
28 return true;
29 }
30
data()31 CharType* data() { return mBuffer.get(); }
32
capacity() const33 size_t capacity() const { return mCapacity; }
34
written(size_t amount)35 void written(size_t amount) { mWritten = amount; }
36
37 size_t mWritten = 0;
38 size_t mCapacity = 0;
39
40 struct FreePolicy {
operator ()mozilla::intl::SizeableUTF8Buffer::FreePolicy41 void operator()(const void* ptr) { free(const_cast<void*>(ptr)); }
42 };
43
44 UniquePtr<CharType[], FreePolicy> mBuffer;
45 };
46
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentPattern,mParent)47 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentPattern, mParent)
48
49 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentPattern, AddRef)
50 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentPattern, Release)
51
52 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId)
53 : mId(aId), mParent(aParent) {
54 MOZ_COUNT_CTOR(FluentPattern);
55 }
FluentPattern(nsISupports * aParent,const nsACString & aId,const nsACString & aAttrName)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
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)62 JSObject* FluentPattern::WrapObject(JSContext* aCx,
63 JS::Handle<JSObject*> aGivenProto) {
64 return FluentPattern_Binding::Wrap(aCx, this, aGivenProto);
65 }
66
~FluentPattern()67 FluentPattern::~FluentPattern() { MOZ_COUNT_DTOR(FluentPattern); };
68
69 /* FluentBundle */
70
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle,mParent)71 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle, mParent)
72
73 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentBundle, AddRef)
74 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentBundle, Release)
75
76 FluentBundle::FluentBundle(nsISupports* aParent,
77 UniquePtr<ffi::FluentBundleRc> aRaw)
78 : mParent(aParent), mRaw(std::move(aRaw)) {
79 MOZ_COUNT_CTOR(FluentBundle);
80 }
81
Constructor(const dom::GlobalObject & aGlobal,const UTF8StringOrUTF8StringSequence & aLocales,const dom::FluentBundleOptions & aOptions,ErrorResult & aRv)82 already_AddRefed<FluentBundle> FluentBundle::Constructor(
83 const dom::GlobalObject& aGlobal,
84 const UTF8StringOrUTF8StringSequence& aLocales,
85 const dom::FluentBundleOptions& aOptions, ErrorResult& aRv) {
86 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
87 if (!global) {
88 aRv.Throw(NS_ERROR_FAILURE);
89 return nullptr;
90 }
91
92 bool useIsolating = aOptions.mUseIsolating;
93
94 nsAutoCString pseudoStrategy;
95 if (aOptions.mPseudoStrategy.WasPassed()) {
96 pseudoStrategy = aOptions.mPseudoStrategy.Value();
97 }
98
99 UniquePtr<ffi::FluentBundleRc> raw;
100
101 if (aLocales.IsUTF8String()) {
102 const nsACString& locale = aLocales.GetAsUTF8String();
103 raw.reset(
104 ffi::fluent_bundle_new_single(&locale, useIsolating, &pseudoStrategy));
105 } else {
106 const auto& locales = aLocales.GetAsUTF8StringSequence();
107 raw.reset(ffi::fluent_bundle_new(locales.Elements(), locales.Length(),
108 useIsolating, &pseudoStrategy));
109 }
110
111 if (!raw) {
112 aRv.ThrowInvalidStateError(
113 "Failed to create the FluentBundle. Check the "
114 "locales and pseudo strategy arguments.");
115 return nullptr;
116 }
117
118 return do_AddRef(new FluentBundle(global, std::move(raw)));
119 }
120
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)121 JSObject* FluentBundle::WrapObject(JSContext* aCx,
122 JS::Handle<JSObject*> aGivenProto) {
123 return FluentBundle_Binding::Wrap(aCx, this, aGivenProto);
124 }
125
~FluentBundle()126 FluentBundle::~FluentBundle() { MOZ_COUNT_DTOR(FluentBundle); };
127
GetLocales(nsTArray<nsCString> & aLocales)128 void FluentBundle::GetLocales(nsTArray<nsCString>& aLocales) {
129 fluent_bundle_get_locales(mRaw.get(), &aLocales);
130 }
131
AddResource(FluentResource & aResource,const dom::FluentBundleAddResourceOptions & aOptions)132 void FluentBundle::AddResource(
133 FluentResource& aResource,
134 const dom::FluentBundleAddResourceOptions& aOptions) {
135 bool allowOverrides = aOptions.mAllowOverrides;
136 nsTArray<nsCString> errors;
137
138 fluent_bundle_add_resource(mRaw.get(), aResource.Raw(), allowOverrides,
139 &errors);
140
141 for (auto& err : errors) {
142 nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err), "L10n",
143 false, true,
144 nsIScriptError::warningFlag);
145 }
146 }
147
HasMessage(const nsACString & aId)148 bool FluentBundle::HasMessage(const nsACString& aId) {
149 return fluent_bundle_has_message(mRaw.get(), &aId);
150 }
151
GetMessage(const nsACString & aId,Nullable<FluentMessage> & aRetVal)152 void FluentBundle::GetMessage(const nsACString& aId,
153 Nullable<FluentMessage>& aRetVal) {
154 bool hasValue = false;
155 nsTArray<nsCString> attributes;
156 bool exists =
157 fluent_bundle_get_message(mRaw.get(), &aId, &hasValue, &attributes);
158 if (exists) {
159 FluentMessage& msg = aRetVal.SetValue();
160 if (hasValue) {
161 msg.mValue = new FluentPattern(mParent, aId);
162 }
163 for (auto& name : attributes) {
164 auto newEntry = msg.mAttributes.Entries().AppendElement(fallible);
165 newEntry->mKey = name;
166 newEntry->mValue = new FluentPattern(mParent, aId, name);
167 }
168 }
169 }
170
extendJSArrayWithErrors(JSContext * aCx,JS::Handle<JSObject * > aErrors,nsTArray<nsCString> & aInput)171 bool extendJSArrayWithErrors(JSContext* aCx, JS::Handle<JSObject*> aErrors,
172 nsTArray<nsCString>& aInput) {
173 uint32_t length;
174 if (NS_WARN_IF(!JS::GetArrayLength(aCx, aErrors, &length))) {
175 return false;
176 }
177
178 for (auto& err : aInput) {
179 JS::Rooted<JS::Value> jsval(aCx);
180 if (!ToJSValue(aCx, NS_ConvertUTF8toUTF16(err), &jsval)) {
181 return false;
182 }
183 if (!JS_DefineElement(aCx, aErrors, length++, jsval, JSPROP_ENUMERATE)) {
184 return false;
185 }
186 }
187 return true;
188 }
189
FormatPattern(JSContext * aCx,const FluentPattern & aPattern,const Nullable<L10nArgs> & aArgs,const Optional<JS::Handle<JSObject * >> & aErrors,nsACString & aRetVal,ErrorResult & aRv)190 void FluentBundle::FormatPattern(JSContext* aCx, const FluentPattern& aPattern,
191 const Nullable<L10nArgs>& aArgs,
192 const Optional<JS::Handle<JSObject*>>& aErrors,
193 nsACString& aRetVal, ErrorResult& aRv) {
194 nsTArray<nsCString> argIds;
195 nsTArray<ffi::FluentArgument> argValues;
196
197 if (!aArgs.IsNull()) {
198 const L10nArgs& args = aArgs.Value();
199 for (auto& entry : args.Entries()) {
200 if (!entry.mValue.IsNull()) {
201 argIds.AppendElement(entry.mKey);
202
203 auto& value = entry.mValue.Value();
204 if (value.IsUTF8String()) {
205 argValues.AppendElement(
206 ffi::FluentArgument::String(&value.GetAsUTF8String()));
207 } else {
208 argValues.AppendElement(
209 ffi::FluentArgument::Double_(value.GetAsDouble()));
210 }
211 }
212 }
213 }
214
215 nsTArray<nsCString> errors;
216 bool succeeded = fluent_bundle_format_pattern(mRaw.get(), &aPattern.mId,
217 &aPattern.mAttrName, &argIds,
218 &argValues, &aRetVal, &errors);
219
220 if (!succeeded) {
221 return aRv.ThrowInvalidStateError(
222 "Failed to format the FluentPattern. Likely the "
223 "pattern could not be retrieved from the bundle.");
224 }
225
226 if (aErrors.WasPassed()) {
227 if (!extendJSArrayWithErrors(aCx, aErrors.Value(), errors)) {
228 aRv.ThrowUnknownError("Failed to add errors to an error array.");
229 }
230 }
231 }
232
233 // FFI
234
235 extern "C" {
FluentBuiltInNumberFormatterCreate(const nsCString * aLocale,const ffi::FluentNumberOptionsRaw * aOptions)236 ffi::RawNumberFormatter* FluentBuiltInNumberFormatterCreate(
237 const nsCString* aLocale, const ffi::FluentNumberOptionsRaw* aOptions) {
238 NumberFormatOptions options;
239 switch (aOptions->style) {
240 case ffi::FluentNumberStyleRaw::Decimal:
241 break;
242 case ffi::FluentNumberStyleRaw::Currency: {
243 std::string currency = aOptions->currency.get();
244 switch (aOptions->currency_display) {
245 case ffi::FluentNumberCurrencyDisplayStyleRaw::Symbol:
246 options.mCurrency = Some(std::make_pair(
247 currency, NumberFormatOptions::CurrencyDisplay::Symbol));
248 break;
249 case ffi::FluentNumberCurrencyDisplayStyleRaw::Code:
250 options.mCurrency = Some(std::make_pair(
251 currency, NumberFormatOptions::CurrencyDisplay::Code));
252 break;
253 case ffi::FluentNumberCurrencyDisplayStyleRaw::Name:
254 options.mCurrency = Some(std::make_pair(
255 currency, NumberFormatOptions::CurrencyDisplay::Name));
256 break;
257 default:
258 MOZ_ASSERT_UNREACHABLE();
259 break;
260 }
261 } break;
262 case ffi::FluentNumberStyleRaw::Percent:
263 options.mPercent = true;
264 break;
265 default:
266 MOZ_ASSERT_UNREACHABLE();
267 break;
268 }
269
270 options.mUseGrouping = aOptions->use_grouping;
271 options.mMinIntegerDigits = Some(aOptions->minimum_integer_digits);
272
273 if (aOptions->minimum_significant_digits >= 0 ||
274 aOptions->maximum_significant_digits >= 0) {
275 options.mSignificantDigits =
276 Some(std::make_pair(aOptions->minimum_significant_digits,
277 aOptions->maximum_significant_digits));
278 } else {
279 options.mFractionDigits = Some(std::make_pair(
280 aOptions->minimum_fraction_digits, aOptions->maximum_fraction_digits));
281 }
282
283 Result<UniquePtr<NumberFormat>, NumberFormat::FormatError> result =
284 NumberFormat::TryCreate(aLocale->get(), options);
285
286 MOZ_ASSERT(result.isOk());
287
288 if (result.isOk()) {
289 return reinterpret_cast<ffi::RawNumberFormatter*>(
290 result.unwrap().release());
291 }
292
293 return nullptr;
294 }
295
FluentBuiltInNumberFormatterFormat(const ffi::RawNumberFormatter * aFormatter,double input,size_t * aOutCount,size_t * aOutCapacity)296 uint8_t* FluentBuiltInNumberFormatterFormat(
297 const ffi::RawNumberFormatter* aFormatter, double input, size_t* aOutCount,
298 size_t* aOutCapacity) {
299 const NumberFormat* nf = reinterpret_cast<const NumberFormat*>(aFormatter);
300
301 SizeableUTF8Buffer buffer;
302 if (nf->format(input, buffer).isOk()) {
303 *aOutCount = buffer.mWritten;
304 *aOutCapacity = buffer.mCapacity;
305 return buffer.mBuffer.release();
306 }
307
308 return nullptr;
309 }
310
FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter * aFormatter)311 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter* aFormatter) {
312 delete reinterpret_cast<NumberFormat*>(aFormatter);
313 }
314
315 /* DateTime */
316
GetStyle(ffi::FluentDateTimeStyle aStyle)317 static DateTimeStyle GetStyle(ffi::FluentDateTimeStyle aStyle) {
318 switch (aStyle) {
319 case ffi::FluentDateTimeStyle::Full:
320 return DateTimeStyle::Full;
321 case ffi::FluentDateTimeStyle::Long:
322 return DateTimeStyle::Long;
323 case ffi::FluentDateTimeStyle::Medium:
324 return DateTimeStyle::Medium;
325 case ffi::FluentDateTimeStyle::Short:
326 return DateTimeStyle::Short;
327 case ffi::FluentDateTimeStyle::None:
328 return DateTimeStyle::None;
329 default:
330 MOZ_ASSERT_UNREACHABLE("Unsupported date time style.");
331 return DateTimeStyle::None;
332 }
333 }
334
FluentBuiltInDateTimeFormatterCreate(const nsCString * aLocale,const ffi::FluentDateTimeOptionsRaw * aOptions)335 ffi::RawDateTimeFormatter* FluentBuiltInDateTimeFormatterCreate(
336 const nsCString* aLocale, const ffi::FluentDateTimeOptionsRaw* aOptions) {
337 if (aOptions->date_style == ffi::FluentDateTimeStyle::None &&
338 aOptions->time_style == ffi::FluentDateTimeStyle::None &&
339 !aOptions->skeleton.IsEmpty()) {
340 auto result = DateTimeFormat::TryCreateFromSkeleton(
341 Span(aLocale->get(), aLocale->Length()),
342 Span(aOptions->skeleton.get(), aOptions->skeleton.Length()));
343 if (result.isErr()) {
344 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
345 return nullptr;
346 }
347
348 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
349 result.unwrap().release());
350 }
351
352 auto result = DateTimeFormat::TryCreateFromStyle(
353 Span(aLocale->get(), aLocale->Length()), GetStyle(aOptions->date_style),
354 GetStyle(aOptions->time_style));
355
356 if (result.isErr()) {
357 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
358 return nullptr;
359 }
360
361 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
362 result.unwrap().release());
363 }
364
FluentBuiltInDateTimeFormatterFormat(const ffi::RawDateTimeFormatter * aFormatter,double aUnixEpoch,uint32_t * aOutCount)365 uint8_t* FluentBuiltInDateTimeFormatterFormat(
366 const ffi::RawDateTimeFormatter* aFormatter, double aUnixEpoch,
367 uint32_t* aOutCount) {
368 const auto* dtFormat = reinterpret_cast<const DateTimeFormat*>(aFormatter);
369
370 SizeableUTF8Buffer buffer;
371 dtFormat->TryFormat(aUnixEpoch, buffer).unwrap();
372
373 *aOutCount = buffer.mWritten;
374
375 return buffer.mBuffer.release();
376 }
377
FluentBuiltInDateTimeFormatterDestroy(ffi::RawDateTimeFormatter * aFormatter)378 void FluentBuiltInDateTimeFormatterDestroy(
379 ffi::RawDateTimeFormatter* aFormatter) {
380 delete reinterpret_cast<const DateTimeFormat*>(aFormatter);
381 }
382 }
383
384 } // namespace intl
385 } // namespace mozilla
386