Bug 1867190 - Initialise the PHC allocate delay later r=glandium
[gecko.git] / dom / l10n / DOMLocalization.cpp
blobb4fcf149c6d71eb913bd12c010fa0cccb4c4a02a
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 "json/json.h"
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 bool DOMLocalization::HasPendingMutations() const {
100 return mMutations && mMutations->HasPendingMutations();
104 * DOMLocalization API
107 void DOMLocalization::ConnectRoot(nsINode& aNode) {
108 nsCOMPtr<nsIGlobalObject> global = aNode.GetOwnerGlobal();
109 if (!global) {
110 return;
112 MOZ_ASSERT(global == mGlobal,
113 "Cannot add a root that overlaps with existing root.");
115 #ifdef DEBUG
116 for (nsINode* root : mRoots) {
117 MOZ_ASSERT(
118 root != &aNode && !root->Contains(&aNode) && !aNode.Contains(root),
119 "Cannot add a root that overlaps with existing root.");
121 #endif
123 mRoots.Insert(&aNode);
125 aNode.AddMutationObserverUnlessExists(mMutations);
128 void DOMLocalization::DisconnectRoot(nsINode& aNode) {
129 if (mRoots.Contains(&aNode)) {
130 aNode.RemoveMutationObserver(mMutations);
131 mRoots.Remove(&aNode);
135 void DOMLocalization::PauseObserving() { mMutations->PauseObserving(); }
137 void DOMLocalization::ResumeObserving() { 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 (aArgs.WasPassed() && aArgs.Value()) {
143 nsAutoString data;
144 JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value()));
145 if (!nsContentUtils::StringifyJSON(aCx, val, data,
146 UndefinedIsNullStringLiteral)) {
147 aRv.NoteJSContextException(aCx);
148 return;
150 if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nargs, data,
151 eCaseMatters)) {
152 aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, true);
154 } else {
155 aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, true);
158 if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nid, aId,
159 eCaseMatters)) {
160 aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, aId, true);
164 void DOMLocalization::GetAttributes(Element& aElement, L10nIdArgs& aResult,
165 ErrorResult& aRv) {
166 nsAutoString l10nId;
167 nsAutoString l10nArgs;
169 if (aElement.GetAttr(nsGkAtoms::datal10nid, l10nId)) {
170 CopyUTF16toUTF8(l10nId, aResult.mId);
173 if (aElement.GetAttr(nsGkAtoms::datal10nargs, l10nArgs)) {
174 ConvertStringToL10nArgs(l10nArgs, aResult.mArgs.SetValue(), aRv);
178 void DOMLocalization::SetArgs(JSContext* aCx, Element& aElement,
179 const Optional<JS::Handle<JSObject*>>& aArgs,
180 ErrorResult& aRv) {
181 if (aArgs.WasPassed() && aArgs.Value()) {
182 nsAutoString data;
183 JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value()));
184 if (!nsContentUtils::StringifyJSON(aCx, val, data,
185 UndefinedIsNullStringLiteral)) {
186 aRv.NoteJSContextException(aCx);
187 return;
189 if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nargs, data,
190 eCaseMatters)) {
191 aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, true);
193 } else {
194 aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, true);
198 already_AddRefed<Promise> DOMLocalization::TranslateFragment(nsINode& aNode,
199 ErrorResult& aRv) {
200 Sequence<OwningNonNull<Element>> elements;
201 GetTranslatables(aNode, elements, aRv);
202 if (NS_WARN_IF(aRv.Failed())) {
203 return nullptr;
205 return TranslateElements(elements, aRv);
209 * A Promise Handler used to apply the result of
210 * a call to Localization::FormatMessages onto the list
211 * of translatable elements.
213 class ElementTranslationHandler : public PromiseNativeHandler {
214 public:
215 explicit ElementTranslationHandler(DOMLocalization* aDOMLocalization,
216 nsXULPrototypeDocument* aProto)
217 : mDOMLocalization(aDOMLocalization), mProto(aProto){};
219 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
220 NS_DECL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
222 nsTArray<nsCOMPtr<Element>>& Elements() { return mElements; }
224 void SetReturnValuePromise(Promise* aReturnValuePromise) {
225 mReturnValuePromise = aReturnValuePromise;
228 virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
229 ErrorResult& aRv) override {
230 ErrorResult rv;
232 nsTArray<Nullable<L10nMessage>> l10nData;
233 if (aValue.isObject()) {
234 JS::ForOfIterator iter(aCx);
235 if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
236 mReturnValuePromise->MaybeRejectWithUndefined();
237 return;
239 if (!iter.valueIsIterable()) {
240 mReturnValuePromise->MaybeRejectWithUndefined();
241 return;
244 JS::Rooted<JS::Value> temp(aCx);
245 while (true) {
246 bool done;
247 if (!iter.next(&temp, &done)) {
248 mReturnValuePromise->MaybeRejectWithUndefined();
249 return;
252 if (done) {
253 break;
256 Nullable<L10nMessage>* slotPtr =
257 l10nData.AppendElement(mozilla::fallible);
258 if (!slotPtr) {
259 mReturnValuePromise->MaybeRejectWithUndefined();
260 return;
263 if (!temp.isNull()) {
264 if (!slotPtr->SetValue().Init(aCx, temp)) {
265 mReturnValuePromise->MaybeRejectWithUndefined();
266 return;
272 bool allTranslated =
273 mDOMLocalization->ApplyTranslations(mElements, l10nData, mProto, rv);
274 if (NS_WARN_IF(rv.Failed()) || !allTranslated) {
275 mReturnValuePromise->MaybeRejectWithUndefined();
276 return;
279 mReturnValuePromise->MaybeResolveWithUndefined();
282 virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
283 ErrorResult& aRv) override {
284 mReturnValuePromise->MaybeRejectWithClone(aCx, aValue);
287 private:
288 ~ElementTranslationHandler() = default;
290 nsTArray<nsCOMPtr<Element>> mElements;
291 RefPtr<DOMLocalization> mDOMLocalization;
292 RefPtr<Promise> mReturnValuePromise;
293 RefPtr<nsXULPrototypeDocument> mProto;
296 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementTranslationHandler)
297 NS_INTERFACE_MAP_ENTRY(nsISupports)
298 NS_INTERFACE_MAP_END
300 NS_IMPL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
302 NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementTranslationHandler)
303 NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementTranslationHandler)
305 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementTranslationHandler)
306 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
307 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMLocalization)
308 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValuePromise)
309 NS_IMPL_CYCLE_COLLECTION_UNLINK(mProto)
310 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
312 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementTranslationHandler)
313 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
314 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMLocalization)
315 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValuePromise)
316 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProto)
317 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
319 already_AddRefed<Promise> DOMLocalization::TranslateElements(
320 const nsTArray<OwningNonNull<Element>>& aElements, ErrorResult& aRv) {
321 return TranslateElements(aElements, nullptr, aRv);
324 already_AddRefed<Promise> DOMLocalization::TranslateElements(
325 const nsTArray<OwningNonNull<Element>>& aElements,
326 nsXULPrototypeDocument* aProto, ErrorResult& aRv) {
327 Sequence<OwningUTF8StringOrL10nIdArgs> l10nKeys;
328 RefPtr<ElementTranslationHandler> nativeHandler =
329 new ElementTranslationHandler(this, aProto);
330 nsTArray<nsCOMPtr<Element>>& domElements = nativeHandler->Elements();
331 domElements.SetCapacity(aElements.Length());
333 if (!mGlobal) {
334 aRv.Throw(NS_ERROR_UNEXPECTED);
335 return nullptr;
338 for (auto& domElement : aElements) {
339 if (!domElement->HasAttr(nsGkAtoms::datal10nid)) {
340 continue;
343 OwningUTF8StringOrL10nIdArgs* key = l10nKeys.AppendElement(fallible);
344 if (!key) {
345 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
346 return nullptr;
349 GetAttributes(*domElement, key->SetAsL10nIdArgs(), aRv);
350 if (NS_WARN_IF(aRv.Failed())) {
351 return nullptr;
354 if (!domElements.AppendElement(domElement, fallible)) {
355 // This can't really happen, we SetCapacity'd above...
356 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
357 return nullptr;
361 RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
362 if (NS_WARN_IF(aRv.Failed())) {
363 return nullptr;
366 if (IsSync()) {
367 nsTArray<Nullable<L10nMessage>> l10nMessages;
369 FormatMessagesSync(l10nKeys, l10nMessages, aRv);
371 if (NS_WARN_IF(aRv.Failed())) {
372 promise->MaybeRejectWithUndefined();
373 return promise.forget();
376 bool allTranslated =
377 ApplyTranslations(domElements, l10nMessages, aProto, aRv);
378 if (NS_WARN_IF(aRv.Failed()) || !allTranslated) {
379 promise->MaybeRejectWithUndefined();
380 return promise.forget();
383 promise->MaybeResolveWithUndefined();
384 return promise.forget();
386 RefPtr<Promise> callbackResult = FormatMessages(l10nKeys, aRv);
387 if (NS_WARN_IF(aRv.Failed())) {
388 return nullptr;
390 nativeHandler->SetReturnValuePromise(promise);
391 callbackResult->AppendNativeHandler(nativeHandler);
392 return MaybeWrapPromise(promise);
396 * Promise handler used to set localization data on
397 * roots of elements that got successfully translated.
399 class L10nRootTranslationHandler final : public PromiseNativeHandler {
400 public:
401 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
402 NS_DECL_CYCLE_COLLECTION_CLASS(L10nRootTranslationHandler)
404 explicit L10nRootTranslationHandler(Element* aRoot) : mRoot(aRoot) {}
406 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
407 ErrorResult& aRv) override {
408 DOMLocalization::SetRootInfo(mRoot);
411 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
412 ErrorResult& aRv) override {}
414 private:
415 ~L10nRootTranslationHandler() = default;
417 RefPtr<Element> mRoot;
420 NS_IMPL_CYCLE_COLLECTION(L10nRootTranslationHandler, mRoot)
422 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nRootTranslationHandler)
423 NS_INTERFACE_MAP_ENTRY(nsISupports)
424 NS_INTERFACE_MAP_END
426 NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nRootTranslationHandler)
427 NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nRootTranslationHandler)
429 already_AddRefed<Promise> DOMLocalization::TranslateRoots(ErrorResult& aRv) {
430 nsTArray<RefPtr<Promise>> promises;
432 for (nsINode* root : mRoots) {
433 RefPtr<Promise> promise = TranslateFragment(*root, aRv);
434 if (MOZ_UNLIKELY(aRv.Failed())) {
435 return nullptr;
438 // If the root is an element, we'll add a native handler
439 // to set root info (language, direction etc.) on it
440 // once the localization finishes.
441 if (root->IsElement()) {
442 RefPtr<L10nRootTranslationHandler> nativeHandler =
443 new L10nRootTranslationHandler(root->AsElement());
444 promise->AppendNativeHandler(nativeHandler);
447 promises.AppendElement(promise);
449 AutoEntryScript aes(mGlobal, "DOMLocalization TranslateRoots");
450 return Promise::All(aes.cx(), promises, aRv);
454 * Helper methods
457 /* static */
458 void DOMLocalization::GetTranslatables(
459 nsINode& aNode, Sequence<OwningNonNull<Element>>& aElements,
460 ErrorResult& aRv) {
461 nsIContent* node =
462 aNode.IsContent() ? aNode.AsContent() : aNode.GetFirstChild();
463 for (; node; node = node->GetNextNode(&aNode)) {
464 if (!node->IsElement()) {
465 continue;
468 Element* domElement = node->AsElement();
470 if (!domElement->HasAttr(nsGkAtoms::datal10nid)) {
471 continue;
474 if (!aElements.AppendElement(*domElement, fallible)) {
475 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
476 return;
481 /* static */
482 void DOMLocalization::SetRootInfo(Element* aElement) {
483 nsAutoCString primaryLocale;
484 LocaleService::GetInstance()->GetAppLocaleAsBCP47(primaryLocale);
485 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::lang,
486 NS_ConvertUTF8toUTF16(primaryLocale), true);
488 nsAutoString dir;
489 if (LocaleService::GetInstance()->IsAppLocaleRTL()) {
490 nsGkAtoms::rtl->ToString(dir);
491 } else {
492 nsGkAtoms::ltr->ToString(dir);
495 uint32_t nameSpace = aElement->GetNameSpaceID();
496 nsAtom* dirAtom =
497 nameSpace == kNameSpaceID_XUL ? nsGkAtoms::localedir : nsGkAtoms::dir;
499 aElement->SetAttr(kNameSpaceID_None, dirAtom, dir, true);
502 bool DOMLocalization::ApplyTranslations(
503 nsTArray<nsCOMPtr<Element>>& aElements,
504 nsTArray<Nullable<L10nMessage>>& aTranslations,
505 nsXULPrototypeDocument* aProto, ErrorResult& aRv) {
506 if (aElements.Length() != aTranslations.Length()) {
507 aRv.Throw(NS_ERROR_FAILURE);
508 return false;
511 PauseObserving();
513 bool hasMissingTranslation = false;
515 nsTArray<L10nOverlaysError> errors;
516 for (size_t i = 0; i < aTranslations.Length(); ++i) {
517 nsCOMPtr elem = aElements[i];
518 if (aTranslations[i].IsNull()) {
519 hasMissingTranslation = true;
520 continue;
522 // If we have a proto, we expect all elements are connected up.
523 // If they're not, they may have been removed by earlier translations.
524 // We will have added an error in L10nOverlays in this case.
525 // This is an error in fluent use, but shouldn't be crashing. There's
526 // also no point translating the element - skip it:
527 if (aProto && !elem->IsInComposedDoc()) {
528 continue;
531 // It is possible that someone removed the `data-l10n-id` from the element
532 // before the async translation completed. In that case, skip applying
533 // the translation.
534 if (!elem->HasAttr(nsGkAtoms::datal10nid)) {
535 continue;
537 L10nOverlays::TranslateElement(*elem, aTranslations[i].Value(), errors,
538 aRv);
539 if (NS_WARN_IF(aRv.Failed())) {
540 hasMissingTranslation = true;
541 continue;
543 if (aProto) {
544 // We only need to rebuild deep if the translation has a value.
545 // Otherwise we'll only rebuild the attributes.
546 aProto->RebuildL10nPrototype(elem,
547 !aTranslations[i].Value().mValue.IsVoid());
551 ReportL10nOverlaysErrors(errors);
553 ResumeObserving();
555 return !hasMissingTranslation;
558 /* Protected */
560 void DOMLocalization::OnChange() {
561 Localization::OnChange();
562 RefPtr<Promise> promise = TranslateRoots(IgnoreErrors());
565 void DOMLocalization::DisconnectMutations() {
566 if (mMutations) {
567 mMutations->Disconnect();
568 DisconnectRoots();
572 void DOMLocalization::DisconnectRoots() {
573 for (nsINode* node : mRoots) {
574 node->RemoveMutationObserver(mMutations);
576 mRoots.Clear();
579 void DOMLocalization::ReportL10nOverlaysErrors(
580 nsTArray<L10nOverlaysError>& aErrors) {
581 nsAutoString msg;
583 for (auto& error : aErrors) {
584 if (error.mCode.WasPassed()) {
585 msg = u"[fluent-dom] "_ns;
586 switch (error.mCode.Value()) {
587 case L10nOverlays_Binding::ERROR_FORBIDDEN_TYPE:
588 msg += u"An element of forbidden type \""_ns +
589 error.mTranslatedElementName.Value() +
590 nsLiteralString(
591 u"\" was found in the translation. Only safe text-level "
592 "elements and elements with data-l10n-name are allowed.");
593 break;
594 case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_MISSING:
595 msg += u"An element named \""_ns + error.mL10nName.Value() +
596 u"\" wasn't found in the source."_ns;
597 break;
598 case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_TYPE_MISMATCH:
599 msg += u"An element named \""_ns + error.mL10nName.Value() +
600 nsLiteralString(
601 u"\" was found in the translation but its type ") +
602 error.mTranslatedElementName.Value() +
603 nsLiteralString(
604 u" didn't match the element found in the source ") +
605 error.mSourceElementName.Value() + u"."_ns;
606 break;
607 case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISCONNECTED:
608 msg += u"The element using message \""_ns + error.mL10nName.Value() +
609 nsLiteralString(
610 u"\" was removed from the DOM when translating its \"") +
611 error.mTranslatedElementName.Value() + u"\" parent."_ns;
612 break;
613 case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISALLOWED_DOM:
614 msg += nsLiteralString(
615 u"While translating an element with fluent ID \"") +
616 error.mL10nName.Value() + u"\" a child element of type \""_ns +
617 error.mTranslatedElementName.Value() +
618 nsLiteralString(
619 u"\" was removed. Either the fluent message "
620 "does not contain markup, or it does not contain markup "
621 "of this type.");
622 break;
623 case L10nOverlays_Binding::ERROR_UNKNOWN:
624 default:
625 msg += nsLiteralString(
626 u"Unknown error happened while translating an element.");
627 break;
629 nsPIDOMWindowInner* innerWindow = GetParentObject()->GetAsInnerWindow();
630 Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
631 if (doc) {
632 nsContentUtils::ReportToConsoleNonLocalized(
633 msg, nsIScriptError::warningFlag, "DOM"_ns, doc);
634 } else {
635 NS_WARNING("Failed to report l10n DOM Overlay errors to console.");
637 printf_stderr("%s\n", NS_ConvertUTF16toUTF8(msg).get());
642 void DOMLocalization::ConvertStringToL10nArgs(const nsString& aInput,
643 intl::L10nArgs& aRetVal,
644 ErrorResult& aRv) {
645 if (aInput.IsEmpty()) {
646 // There are no properties.
647 return;
650 Json::Value args;
651 Json::Reader jsonReader;
653 if (!jsonReader.parse(NS_ConvertUTF16toUTF8(aInput).get(), args, false)) {
654 nsTArray<nsCString> errors{
655 "[dom/l10n] Failed to parse l10n-args JSON: "_ns +
656 NS_ConvertUTF16toUTF8(aInput),
658 MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
659 return;
662 if (!args.isObject()) {
663 nsTArray<nsCString> errors{
664 "[dom/l10n] Failed to parse l10n-args JSON: "_ns +
665 NS_ConvertUTF16toUTF8(aInput),
667 MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
668 return;
671 for (Json::ValueConstIterator iter = args.begin(); iter != args.end();
672 ++iter) {
673 L10nArgs::EntryType* newEntry = aRetVal.Entries().AppendElement(fallible);
674 if (!newEntry) {
675 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
676 return;
678 newEntry->mKey = iter.name().c_str();
679 if (iter->isString()) {
680 newEntry->mValue.SetValue().RawSetAsUTF8String().Assign(
681 iter->asString().c_str(), iter->asString().length());
682 } else if (iter->isDouble()) {
683 newEntry->mValue.SetValue().RawSetAsDouble() = iter->asDouble();
684 } else if (iter->isBool()) {
685 if (iter->asBool()) {
686 newEntry->mValue.SetValue().RawSetAsUTF8String().Assign("true");
687 } else {
688 newEntry->mValue.SetValue().RawSetAsUTF8String().Assign("false");
690 } else if (iter->isNull()) {
691 newEntry->mValue.SetNull();
692 } else {
693 nsTArray<nsCString> errors{
694 "[dom/l10n] Failed to convert l10n-args JSON: "_ns +
695 NS_ConvertUTF16toUTF8(aInput),
697 MaybeReportErrorsToGecko(errors, aRv, GetParentObject());