Bug 1769628 [wpt PR 34081] - Update wpt metadata, a=testonly
[gecko.git] / dom / l10n / DOMLocalization.cpp
blobe821f1b29765cd586aa7e590ea2bb0133c9b68ef
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 "js/ForOfIterator.h" // JS::ForOfIterator
8 #include "js/JSON.h" // JS_ParseJSON
9 #include "nsContentUtils.h"
10 #include "nsIScriptError.h"
11 #include "DOMLocalization.h"
12 #include "mozilla/intl/L10nRegistry.h"
13 #include "mozilla/intl/LocaleService.h"
14 #include "mozilla/dom/AutoEntryScript.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/L10nOverlays.h"
18 using namespace mozilla;
19 using namespace mozilla::dom;
20 using namespace mozilla::intl;
22 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMLocalization)
23 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMLocalization, Localization)
24 tmp->DisconnectMutations();
25 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMutations)
26 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoots)
27 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
28 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMLocalization, Localization)
30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMutations)
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoots)
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
34 NS_IMPL_ADDREF_INHERITED(DOMLocalization, Localization)
35 NS_IMPL_RELEASE_INHERITED(DOMLocalization, Localization)
36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMLocalization)
37 NS_INTERFACE_MAP_END_INHERITING(Localization)
39 DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, bool aSync)
40 : Localization(aGlobal, aSync) {
41 mMutations = new L10nMutations(this);
44 DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, bool aIsSync,
45 const ffi::LocalizationRc* aRaw)
46 : Localization(aGlobal, aIsSync, aRaw) {
47 mMutations = new L10nMutations(this);
50 already_AddRefed<DOMLocalization> DOMLocalization::Constructor(
51 const GlobalObject& aGlobal,
52 const Sequence<dom::OwningUTF8StringOrResourceId>& aResourceIds,
53 bool aIsSync, const Optional<NonNull<L10nRegistry>>& aRegistry,
54 const Optional<Sequence<nsCString>>& aLocales, ErrorResult& aRv) {
55 auto ffiResourceIds{L10nRegistry::ResourceIdsToFFI(aResourceIds)};
56 Maybe<nsTArray<nsCString>> locales;
58 if (aLocales.WasPassed()) {
59 locales.emplace();
60 locales->SetCapacity(aLocales.Value().Length());
61 for (const auto& locale : aLocales.Value()) {
62 locales->AppendElement(locale);
66 RefPtr<const ffi::LocalizationRc> raw;
67 bool result;
69 if (aRegistry.WasPassed()) {
70 result = ffi::localization_new_with_locales(
71 &ffiResourceIds, aIsSync, aRegistry.Value().Raw(),
72 locales.ptrOr(nullptr), getter_AddRefs(raw));
73 } else {
74 result = ffi::localization_new_with_locales(&ffiResourceIds, aIsSync,
75 nullptr, locales.ptrOr(nullptr),
76 getter_AddRefs(raw));
79 if (result) {
80 nsCOMPtr<nsIGlobalObject> global =
81 do_QueryInterface(aGlobal.GetAsSupports());
83 return do_AddRef(new DOMLocalization(global, aIsSync, raw));
85 aRv.ThrowInvalidStateError(
86 "Failed to create the Localization. Check the locales arguments.");
87 return nullptr;
90 JSObject* DOMLocalization::WrapObject(JSContext* aCx,
91 JS::Handle<JSObject*> aGivenProto) {
92 return DOMLocalization_Binding::Wrap(aCx, this, aGivenProto);
95 void DOMLocalization::Destroy() { DisconnectMutations(); }
97 DOMLocalization::~DOMLocalization() { Destroy(); }
99 /**
100 * DOMLocalization API
103 void DOMLocalization::ConnectRoot(nsINode& aNode, ErrorResult& aRv) {
104 nsCOMPtr<nsIGlobalObject> global = aNode.GetOwnerGlobal();
105 if (!global) {
106 return;
108 MOZ_ASSERT(global == mGlobal,
109 "Cannot add a root that overlaps with existing root.");
111 #ifdef DEBUG
112 for (nsINode* root : mRoots) {
113 MOZ_ASSERT(
114 root != &aNode && !root->Contains(&aNode) && !aNode.Contains(root),
115 "Cannot add a root that overlaps with existing root.");
117 #endif
119 mRoots.Insert(&aNode);
121 aNode.AddMutationObserverUnlessExists(mMutations);
124 void DOMLocalization::DisconnectRoot(nsINode& aNode, ErrorResult& aRv) {
125 if (mRoots.Contains(&aNode)) {
126 aNode.RemoveMutationObserver(mMutations);
127 mRoots.Remove(&aNode);
131 void DOMLocalization::PauseObserving(ErrorResult& aRv) {
132 mMutations->PauseObserving();
135 void DOMLocalization::ResumeObserving(ErrorResult& aRv) {
136 mMutations->ResumeObserving();
139 void DOMLocalization::SetAttributes(
140 JSContext* aCx, Element& aElement, const nsAString& aId,
141 const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv) {
142 if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nid, aId,
143 eCaseMatters)) {
144 aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, aId, true);
147 if (aArgs.WasPassed() && aArgs.Value()) {
148 nsAutoString data;
149 JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value()));
150 if (!nsContentUtils::StringifyJSON(aCx, &val, data)) {
151 aRv.NoteJSContextException(aCx);
152 return;
154 if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nargs, data,
155 eCaseMatters)) {
156 aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, true);
158 } else {
159 aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, true);
163 void DOMLocalization::GetAttributes(Element& aElement, L10nIdArgs& aResult,
164 ErrorResult& aRv) {
165 nsAutoString l10nId;
166 nsAutoString l10nArgs;
168 if (aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, l10nId)) {
169 CopyUTF16toUTF8(l10nId, aResult.mId);
172 if (aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, l10nArgs)) {
173 ConvertStringToL10nArgs(l10nArgs, aResult.mArgs.SetValue(), aRv);
177 already_AddRefed<Promise> DOMLocalization::TranslateFragment(nsINode& aNode,
178 ErrorResult& aRv) {
179 Sequence<OwningNonNull<Element>> elements;
180 GetTranslatables(aNode, elements, aRv);
181 if (NS_WARN_IF(aRv.Failed())) {
182 return nullptr;
184 return TranslateElements(elements, aRv);
188 * A Promise Handler used to apply the result of
189 * a call to Localization::FormatMessages onto the list
190 * of translatable elements.
192 class ElementTranslationHandler : public PromiseNativeHandler {
193 public:
194 explicit ElementTranslationHandler(DOMLocalization* aDOMLocalization,
195 nsXULPrototypeDocument* aProto)
196 : mDOMLocalization(aDOMLocalization), mProto(aProto){};
198 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
199 NS_DECL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
201 nsTArray<nsCOMPtr<Element>>& Elements() { return mElements; }
203 void SetReturnValuePromise(Promise* aReturnValuePromise) {
204 mReturnValuePromise = aReturnValuePromise;
207 virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
208 ErrorResult& aRv) override {
209 ErrorResult rv;
211 nsTArray<Nullable<L10nMessage>> l10nData;
212 if (aValue.isObject()) {
213 JS::ForOfIterator iter(aCx);
214 if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
215 mReturnValuePromise->MaybeRejectWithUndefined();
216 return;
218 if (!iter.valueIsIterable()) {
219 mReturnValuePromise->MaybeRejectWithUndefined();
220 return;
223 JS::Rooted<JS::Value> temp(aCx);
224 while (true) {
225 bool done;
226 if (!iter.next(&temp, &done)) {
227 mReturnValuePromise->MaybeRejectWithUndefined();
228 return;
231 if (done) {
232 break;
235 Nullable<L10nMessage>* slotPtr =
236 l10nData.AppendElement(mozilla::fallible);
237 if (!slotPtr) {
238 mReturnValuePromise->MaybeRejectWithUndefined();
239 return;
242 if (!temp.isNull()) {
243 if (!slotPtr->SetValue().Init(aCx, temp)) {
244 mReturnValuePromise->MaybeRejectWithUndefined();
245 return;
251 bool allTranslated =
252 mDOMLocalization->ApplyTranslations(mElements, l10nData, mProto, rv);
253 if (NS_WARN_IF(rv.Failed()) || !allTranslated) {
254 mReturnValuePromise->MaybeRejectWithUndefined();
255 return;
258 mReturnValuePromise->MaybeResolveWithUndefined();
261 virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
262 ErrorResult& aRv) override {
263 mReturnValuePromise->MaybeRejectWithClone(aCx, aValue);
266 private:
267 ~ElementTranslationHandler() = default;
269 nsTArray<nsCOMPtr<Element>> mElements;
270 RefPtr<DOMLocalization> mDOMLocalization;
271 RefPtr<Promise> mReturnValuePromise;
272 RefPtr<nsXULPrototypeDocument> mProto;
275 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementTranslationHandler)
276 NS_INTERFACE_MAP_ENTRY(nsISupports)
277 NS_INTERFACE_MAP_END
279 NS_IMPL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
281 NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementTranslationHandler)
282 NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementTranslationHandler)
284 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementTranslationHandler)
285 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
286 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMLocalization)
287 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValuePromise)
288 NS_IMPL_CYCLE_COLLECTION_UNLINK(mProto)
289 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
291 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementTranslationHandler)
292 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
293 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMLocalization)
294 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValuePromise)
295 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProto)
296 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
298 already_AddRefed<Promise> DOMLocalization::TranslateElements(
299 const nsTArray<OwningNonNull<Element>>& aElements, ErrorResult& aRv) {
300 return TranslateElements(aElements, nullptr, aRv);
303 already_AddRefed<Promise> DOMLocalization::TranslateElements(
304 const nsTArray<OwningNonNull<Element>>& aElements,
305 nsXULPrototypeDocument* aProto, ErrorResult& aRv) {
306 Sequence<OwningUTF8StringOrL10nIdArgs> l10nKeys;
307 RefPtr<ElementTranslationHandler> nativeHandler =
308 new ElementTranslationHandler(this, aProto);
309 nsTArray<nsCOMPtr<Element>>& domElements = nativeHandler->Elements();
310 domElements.SetCapacity(aElements.Length());
312 if (!mGlobal) {
313 aRv.Throw(NS_ERROR_UNEXPECTED);
314 return nullptr;
317 for (auto& domElement : aElements) {
318 if (!domElement->HasAttr(nsGkAtoms::datal10nid)) {
319 continue;
322 OwningUTF8StringOrL10nIdArgs* key = l10nKeys.AppendElement(fallible);
323 if (!key) {
324 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
325 return nullptr;
328 GetAttributes(*domElement, key->SetAsL10nIdArgs(), aRv);
329 if (NS_WARN_IF(aRv.Failed())) {
330 return nullptr;
333 if (!domElements.AppendElement(domElement, fallible)) {
334 // This can't really happen, we SetCapacity'd above...
335 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
336 return nullptr;
340 RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
341 if (NS_WARN_IF(aRv.Failed())) {
342 return nullptr;
345 if (IsSync()) {
346 nsTArray<Nullable<L10nMessage>> l10nMessages;
348 FormatMessagesSync(l10nKeys, l10nMessages, aRv);
350 bool allTranslated =
351 ApplyTranslations(domElements, l10nMessages, aProto, aRv);
352 if (NS_WARN_IF(aRv.Failed()) || !allTranslated) {
353 promise->MaybeRejectWithUndefined();
354 return MaybeWrapPromise(promise);
357 promise->MaybeResolveWithUndefined();
358 } else {
359 RefPtr<Promise> callbackResult = FormatMessages(l10nKeys, aRv);
360 if (NS_WARN_IF(aRv.Failed())) {
361 return nullptr;
363 nativeHandler->SetReturnValuePromise(promise);
364 callbackResult->AppendNativeHandler(nativeHandler);
367 return MaybeWrapPromise(promise);
371 * Promise handler used to set localization data on
372 * roots of elements that got successfully translated.
374 class L10nRootTranslationHandler final : public PromiseNativeHandler {
375 public:
376 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
377 NS_DECL_CYCLE_COLLECTION_CLASS(L10nRootTranslationHandler)
379 explicit L10nRootTranslationHandler(Element* aRoot) : mRoot(aRoot) {}
381 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
382 ErrorResult& aRv) override {
383 DOMLocalization::SetRootInfo(mRoot);
386 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
387 ErrorResult& aRv) override {}
389 private:
390 ~L10nRootTranslationHandler() = default;
392 RefPtr<Element> mRoot;
395 NS_IMPL_CYCLE_COLLECTION(L10nRootTranslationHandler, mRoot)
397 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nRootTranslationHandler)
398 NS_INTERFACE_MAP_ENTRY(nsISupports)
399 NS_INTERFACE_MAP_END
401 NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nRootTranslationHandler)
402 NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nRootTranslationHandler)
404 already_AddRefed<Promise> DOMLocalization::TranslateRoots(ErrorResult& aRv) {
405 nsTArray<RefPtr<Promise>> promises;
407 for (nsINode* root : mRoots) {
408 RefPtr<Promise> promise = TranslateFragment(*root, aRv);
409 if (MOZ_UNLIKELY(aRv.Failed())) {
410 return nullptr;
413 // If the root is an element, we'll add a native handler
414 // to set root info (language, direction etc.) on it
415 // once the localization finishes.
416 if (root->IsElement()) {
417 RefPtr<L10nRootTranslationHandler> nativeHandler =
418 new L10nRootTranslationHandler(root->AsElement());
419 promise->AppendNativeHandler(nativeHandler);
422 promises.AppendElement(promise);
424 AutoEntryScript aes(mGlobal, "DOMLocalization TranslateRoots");
425 return Promise::All(aes.cx(), promises, aRv);
429 * Helper methods
432 /* static */
433 void DOMLocalization::GetTranslatables(
434 nsINode& aNode, Sequence<OwningNonNull<Element>>& aElements,
435 ErrorResult& aRv) {
436 nsIContent* node =
437 aNode.IsContent() ? aNode.AsContent() : aNode.GetFirstChild();
438 for (; node; node = node->GetNextNode(&aNode)) {
439 if (!node->IsElement()) {
440 continue;
443 Element* domElement = node->AsElement();
445 if (!domElement->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
446 continue;
449 if (!aElements.AppendElement(*domElement, fallible)) {
450 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
451 return;
456 /* static */
457 void DOMLocalization::SetRootInfo(Element* aElement) {
458 nsAutoCString primaryLocale;
459 LocaleService::GetInstance()->GetAppLocaleAsBCP47(primaryLocale);
460 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::lang,
461 NS_ConvertUTF8toUTF16(primaryLocale), true);
463 nsAutoString dir;
464 if (LocaleService::GetInstance()->IsAppLocaleRTL()) {
465 nsGkAtoms::rtl->ToString(dir);
466 } else {
467 nsGkAtoms::ltr->ToString(dir);
470 uint32_t nameSpace = aElement->GetNameSpaceID();
471 nsAtom* dirAtom =
472 nameSpace == kNameSpaceID_XUL ? nsGkAtoms::localedir : nsGkAtoms::dir;
474 aElement->SetAttr(kNameSpaceID_None, dirAtom, dir, true);
477 bool DOMLocalization::ApplyTranslations(
478 nsTArray<nsCOMPtr<Element>>& aElements,
479 nsTArray<Nullable<L10nMessage>>& aTranslations,
480 nsXULPrototypeDocument* aProto, ErrorResult& aRv) {
481 if (aElements.Length() != aTranslations.Length()) {
482 aRv.Throw(NS_ERROR_FAILURE);
483 return false;
486 PauseObserving(aRv);
487 if (NS_WARN_IF(aRv.Failed())) {
488 aRv.Throw(NS_ERROR_FAILURE);
489 return false;
492 bool hasMissingTranslation = false;
494 nsTArray<L10nOverlaysError> errors;
495 for (size_t i = 0; i < aTranslations.Length(); ++i) {
496 nsCOMPtr elem = aElements[i];
497 if (aTranslations[i].IsNull()) {
498 hasMissingTranslation = true;
499 continue;
501 // If we have a proto, we expect all elements are connected up.
502 // If they're not, they may have been removed by earlier translations.
503 // We will have added an error in L10nOverlays in this case.
504 // This is an error in fluent use, but shouldn't be crashing. There's
505 // also no point translating the element - skip it:
506 if (aProto && !elem->IsInComposedDoc()) {
507 continue;
510 // It is possible that someone removed the `data-l10n-id` from the element
511 // before the async translation completed. In that case, skip applying
512 // the translation.
513 if (!elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
514 continue;
516 L10nOverlays::TranslateElement(*elem, aTranslations[i].Value(), errors,
517 aRv);
518 if (NS_WARN_IF(aRv.Failed())) {
519 hasMissingTranslation = true;
520 continue;
522 if (aProto) {
523 // We only need to rebuild deep if the translation has a value.
524 // Otherwise we'll only rebuild the attributes.
525 aProto->RebuildL10nPrototype(elem,
526 !aTranslations[i].Value().mValue.IsVoid());
530 ReportL10nOverlaysErrors(errors);
532 ResumeObserving(aRv);
533 if (NS_WARN_IF(aRv.Failed())) {
534 aRv.Throw(NS_ERROR_FAILURE);
535 return false;
538 return !hasMissingTranslation;
541 /* Protected */
543 void DOMLocalization::OnChange() {
544 Localization::OnChange();
545 RefPtr<Promise> promise = TranslateRoots(IgnoreErrors());
548 void DOMLocalization::DisconnectMutations() {
549 if (mMutations) {
550 mMutations->Disconnect();
551 DisconnectRoots();
555 void DOMLocalization::DisconnectRoots() {
556 for (nsINode* node : mRoots) {
557 node->RemoveMutationObserver(mMutations);
559 mRoots.Clear();
562 void DOMLocalization::ReportL10nOverlaysErrors(
563 nsTArray<L10nOverlaysError>& aErrors) {
564 nsAutoString msg;
566 for (auto& error : aErrors) {
567 if (error.mCode.WasPassed()) {
568 msg = u"[fluent-dom] "_ns;
569 switch (error.mCode.Value()) {
570 case L10nOverlays_Binding::ERROR_FORBIDDEN_TYPE:
571 msg += u"An element of forbidden type \""_ns +
572 error.mTranslatedElementName.Value() +
573 nsLiteralString(
574 u"\" was found in the translation. Only safe text-level "
575 "elements and elements with data-l10n-name are allowed.");
576 break;
577 case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_MISSING:
578 msg += u"An element named \""_ns + error.mL10nName.Value() +
579 u"\" wasn't found in the source."_ns;
580 break;
581 case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_TYPE_MISMATCH:
582 msg += u"An element named \""_ns + error.mL10nName.Value() +
583 nsLiteralString(
584 u"\" was found in the translation but its type ") +
585 error.mTranslatedElementName.Value() +
586 nsLiteralString(
587 u" didn't match the element found in the source ") +
588 error.mSourceElementName.Value() + u"."_ns;
589 break;
590 case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISCONNECTED:
591 msg += u"The element using message \""_ns + error.mL10nName.Value() +
592 nsLiteralString(
593 u"\" was removed from the DOM when translating its \"") +
594 error.mTranslatedElementName.Value() + u"\" parent."_ns;
595 break;
596 case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISALLOWED_DOM:
597 msg += nsLiteralString(
598 u"While translating an element with fluent ID \"") +
599 error.mL10nName.Value() + u"\" a child element of type \""_ns +
600 error.mTranslatedElementName.Value() +
601 nsLiteralString(
602 u"\" was removed. Either the fluent message "
603 "does not contain markup, or it does not contain markup "
604 "of this type.");
605 break;
606 case L10nOverlays_Binding::ERROR_UNKNOWN:
607 default:
608 msg += nsLiteralString(
609 u"Unknown error happened while translating an element.");
610 break;
612 nsPIDOMWindowInner* innerWindow = GetParentObject()->AsInnerWindow();
613 Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
614 if (doc) {
615 nsContentUtils::ReportToConsoleNonLocalized(
616 msg, nsIScriptError::warningFlag, "DOM"_ns, doc);
617 } else {
618 NS_WARNING("Failed to report l10n DOM Overlay errors to console.");
620 printf_stderr("%s\n", NS_ConvertUTF16toUTF8(msg).get());
625 void DOMLocalization::ConvertStringToL10nArgs(const nsString& aInput,
626 intl::L10nArgs& aRetVal,
627 ErrorResult& aRv) {
628 // This method uses a temporary dictionary to automate
629 // converting a JSON string into an IDL Record via a dictionary.
631 // Once we get Record::Init(const nsAString& aJSON), we'll switch to
632 // that.
633 L10nArgsHelperDict helperDict;
634 if (!helperDict.Init(u"{\"args\": "_ns + aInput + u"}"_ns)) {
635 nsTArray<nsCString> errors{
636 "[dom/l10n] Failed to parse l10n-args JSON: "_ns +
637 NS_ConvertUTF16toUTF8(aInput),
639 MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
640 return;
642 for (auto& entry : helperDict.mArgs.Entries()) {
643 L10nArgs::EntryType* newEntry = aRetVal.Entries().AppendElement(fallible);
644 if (!newEntry) {
645 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
646 return;
648 newEntry->mKey = entry.mKey;
649 newEntry->mValue = entry.mValue;