diff options
Diffstat (limited to 'dom/l10n/DOMLocalization.cpp')
-rw-r--r-- | dom/l10n/DOMLocalization.cpp | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/dom/l10n/DOMLocalization.cpp b/dom/l10n/DOMLocalization.cpp new file mode 100644 index 0000000000..b4fcf149c6 --- /dev/null +++ b/dom/l10n/DOMLocalization.cpp @@ -0,0 +1,700 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "js/ForOfIterator.h" // JS::ForOfIterator +#include "json/json.h" +#include "nsContentUtils.h" +#include "nsIScriptError.h" +#include "DOMLocalization.h" +#include "mozilla/intl/L10nRegistry.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/L10nOverlays.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::intl; + +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMLocalization) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMLocalization, Localization) + tmp->DisconnectMutations(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMutations) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoots) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMLocalization, Localization) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMutations) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoots) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ADDREF_INHERITED(DOMLocalization, Localization) +NS_IMPL_RELEASE_INHERITED(DOMLocalization, Localization) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMLocalization) +NS_INTERFACE_MAP_END_INHERITING(Localization) + +DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, bool aSync) + : Localization(aGlobal, aSync) { + mMutations = new L10nMutations(this); +} + +DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, bool aIsSync, + const ffi::LocalizationRc* aRaw) + : Localization(aGlobal, aIsSync, aRaw) { + mMutations = new L10nMutations(this); +} + +already_AddRefed<DOMLocalization> DOMLocalization::Constructor( + const GlobalObject& aGlobal, + const Sequence<dom::OwningUTF8StringOrResourceId>& aResourceIds, + bool aIsSync, const Optional<NonNull<L10nRegistry>>& aRegistry, + const Optional<Sequence<nsCString>>& aLocales, ErrorResult& aRv) { + auto ffiResourceIds{L10nRegistry::ResourceIdsToFFI(aResourceIds)}; + Maybe<nsTArray<nsCString>> locales; + + if (aLocales.WasPassed()) { + locales.emplace(); + locales->SetCapacity(aLocales.Value().Length()); + for (const auto& locale : aLocales.Value()) { + locales->AppendElement(locale); + } + } + + RefPtr<const ffi::LocalizationRc> raw; + bool result; + + if (aRegistry.WasPassed()) { + result = ffi::localization_new_with_locales( + &ffiResourceIds, aIsSync, aRegistry.Value().Raw(), + locales.ptrOr(nullptr), getter_AddRefs(raw)); + } else { + result = ffi::localization_new_with_locales(&ffiResourceIds, aIsSync, + nullptr, locales.ptrOr(nullptr), + getter_AddRefs(raw)); + } + + if (result) { + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(aGlobal.GetAsSupports()); + + return do_AddRef(new DOMLocalization(global, aIsSync, raw)); + } + aRv.ThrowInvalidStateError( + "Failed to create the Localization. Check the locales arguments."); + return nullptr; +} + +JSObject* DOMLocalization::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return DOMLocalization_Binding::Wrap(aCx, this, aGivenProto); +} + +void DOMLocalization::Destroy() { DisconnectMutations(); } + +DOMLocalization::~DOMLocalization() { Destroy(); } + +bool DOMLocalization::HasPendingMutations() const { + return mMutations && mMutations->HasPendingMutations(); +} + +/** + * DOMLocalization API + */ + +void DOMLocalization::ConnectRoot(nsINode& aNode) { + nsCOMPtr<nsIGlobalObject> global = aNode.GetOwnerGlobal(); + if (!global) { + return; + } + MOZ_ASSERT(global == mGlobal, + "Cannot add a root that overlaps with existing root."); + +#ifdef DEBUG + for (nsINode* root : mRoots) { + MOZ_ASSERT( + root != &aNode && !root->Contains(&aNode) && !aNode.Contains(root), + "Cannot add a root that overlaps with existing root."); + } +#endif + + mRoots.Insert(&aNode); + + aNode.AddMutationObserverUnlessExists(mMutations); +} + +void DOMLocalization::DisconnectRoot(nsINode& aNode) { + if (mRoots.Contains(&aNode)) { + aNode.RemoveMutationObserver(mMutations); + mRoots.Remove(&aNode); + } +} + +void DOMLocalization::PauseObserving() { mMutations->PauseObserving(); } + +void DOMLocalization::ResumeObserving() { mMutations->ResumeObserving(); } + +void DOMLocalization::SetAttributes( + JSContext* aCx, Element& aElement, const nsAString& aId, + const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv) { + if (aArgs.WasPassed() && aArgs.Value()) { + nsAutoString data; + JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value())); + if (!nsContentUtils::StringifyJSON(aCx, val, data, + UndefinedIsNullStringLiteral)) { + aRv.NoteJSContextException(aCx); + return; + } + if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, + eCaseMatters)) { + aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, true); + } + } else { + aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, true); + } + + if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nid, aId, + eCaseMatters)) { + aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, aId, true); + } +} + +void DOMLocalization::GetAttributes(Element& aElement, L10nIdArgs& aResult, + ErrorResult& aRv) { + nsAutoString l10nId; + nsAutoString l10nArgs; + + if (aElement.GetAttr(nsGkAtoms::datal10nid, l10nId)) { + CopyUTF16toUTF8(l10nId, aResult.mId); + } + + if (aElement.GetAttr(nsGkAtoms::datal10nargs, l10nArgs)) { + ConvertStringToL10nArgs(l10nArgs, aResult.mArgs.SetValue(), aRv); + } +} + +void DOMLocalization::SetArgs(JSContext* aCx, Element& aElement, + const Optional<JS::Handle<JSObject*>>& aArgs, + ErrorResult& aRv) { + if (aArgs.WasPassed() && aArgs.Value()) { + nsAutoString data; + JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value())); + if (!nsContentUtils::StringifyJSON(aCx, val, data, + UndefinedIsNullStringLiteral)) { + aRv.NoteJSContextException(aCx); + return; + } + if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, + eCaseMatters)) { + aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, true); + } + } else { + aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, true); + } +} + +already_AddRefed<Promise> DOMLocalization::TranslateFragment(nsINode& aNode, + ErrorResult& aRv) { + Sequence<OwningNonNull<Element>> elements; + GetTranslatables(aNode, elements, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + return TranslateElements(elements, aRv); +} + +/** + * A Promise Handler used to apply the result of + * a call to Localization::FormatMessages onto the list + * of translatable elements. + */ +class ElementTranslationHandler : public PromiseNativeHandler { + public: + explicit ElementTranslationHandler(DOMLocalization* aDOMLocalization, + nsXULPrototypeDocument* aProto) + : mDOMLocalization(aDOMLocalization), mProto(aProto){}; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler) + + nsTArray<nsCOMPtr<Element>>& Elements() { return mElements; } + + void SetReturnValuePromise(Promise* aReturnValuePromise) { + mReturnValuePromise = aReturnValuePromise; + } + + virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + ErrorResult rv; + + nsTArray<Nullable<L10nMessage>> l10nData; + if (aValue.isObject()) { + JS::ForOfIterator iter(aCx); + if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) { + mReturnValuePromise->MaybeRejectWithUndefined(); + return; + } + if (!iter.valueIsIterable()) { + mReturnValuePromise->MaybeRejectWithUndefined(); + return; + } + + JS::Rooted<JS::Value> temp(aCx); + while (true) { + bool done; + if (!iter.next(&temp, &done)) { + mReturnValuePromise->MaybeRejectWithUndefined(); + return; + } + + if (done) { + break; + } + + Nullable<L10nMessage>* slotPtr = + l10nData.AppendElement(mozilla::fallible); + if (!slotPtr) { + mReturnValuePromise->MaybeRejectWithUndefined(); + return; + } + + if (!temp.isNull()) { + if (!slotPtr->SetValue().Init(aCx, temp)) { + mReturnValuePromise->MaybeRejectWithUndefined(); + return; + } + } + } + } + + bool allTranslated = + mDOMLocalization->ApplyTranslations(mElements, l10nData, mProto, rv); + if (NS_WARN_IF(rv.Failed()) || !allTranslated) { + mReturnValuePromise->MaybeRejectWithUndefined(); + return; + } + + mReturnValuePromise->MaybeResolveWithUndefined(); + } + + virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + mReturnValuePromise->MaybeRejectWithClone(aCx, aValue); + } + + private: + ~ElementTranslationHandler() = default; + + nsTArray<nsCOMPtr<Element>> mElements; + RefPtr<DOMLocalization> mDOMLocalization; + RefPtr<Promise> mReturnValuePromise; + RefPtr<nsXULPrototypeDocument> mProto; +}; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementTranslationHandler) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementTranslationHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementTranslationHandler) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementTranslationHandler) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMLocalization) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValuePromise) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mProto) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementTranslationHandler) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMLocalization) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValuePromise) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProto) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +already_AddRefed<Promise> DOMLocalization::TranslateElements( + const nsTArray<OwningNonNull<Element>>& aElements, ErrorResult& aRv) { + return TranslateElements(aElements, nullptr, aRv); +} + +already_AddRefed<Promise> DOMLocalization::TranslateElements( + const nsTArray<OwningNonNull<Element>>& aElements, + nsXULPrototypeDocument* aProto, ErrorResult& aRv) { + Sequence<OwningUTF8StringOrL10nIdArgs> l10nKeys; + RefPtr<ElementTranslationHandler> nativeHandler = + new ElementTranslationHandler(this, aProto); + nsTArray<nsCOMPtr<Element>>& domElements = nativeHandler->Elements(); + domElements.SetCapacity(aElements.Length()); + + if (!mGlobal) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + for (auto& domElement : aElements) { + if (!domElement->HasAttr(nsGkAtoms::datal10nid)) { + continue; + } + + OwningUTF8StringOrL10nIdArgs* key = l10nKeys.AppendElement(fallible); + if (!key) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + GetAttributes(*domElement, key->SetAsL10nIdArgs(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (!domElements.AppendElement(domElement, fallible)) { + // This can't really happen, we SetCapacity'd above... + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + } + + RefPtr<Promise> promise = Promise::Create(mGlobal, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (IsSync()) { + nsTArray<Nullable<L10nMessage>> l10nMessages; + + FormatMessagesSync(l10nKeys, l10nMessages, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + promise->MaybeRejectWithUndefined(); + return promise.forget(); + } + + bool allTranslated = + ApplyTranslations(domElements, l10nMessages, aProto, aRv); + if (NS_WARN_IF(aRv.Failed()) || !allTranslated) { + promise->MaybeRejectWithUndefined(); + return promise.forget(); + } + + promise->MaybeResolveWithUndefined(); + return promise.forget(); + } + RefPtr<Promise> callbackResult = FormatMessages(l10nKeys, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + nativeHandler->SetReturnValuePromise(promise); + callbackResult->AppendNativeHandler(nativeHandler); + return MaybeWrapPromise(promise); +} + +/** + * Promise handler used to set localization data on + * roots of elements that got successfully translated. + */ +class L10nRootTranslationHandler final : public PromiseNativeHandler { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(L10nRootTranslationHandler) + + explicit L10nRootTranslationHandler(Element* aRoot) : mRoot(aRoot) {} + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + DOMLocalization::SetRootInfo(mRoot); + } + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override {} + + private: + ~L10nRootTranslationHandler() = default; + + RefPtr<Element> mRoot; +}; + +NS_IMPL_CYCLE_COLLECTION(L10nRootTranslationHandler, mRoot) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nRootTranslationHandler) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nRootTranslationHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nRootTranslationHandler) + +already_AddRefed<Promise> DOMLocalization::TranslateRoots(ErrorResult& aRv) { + nsTArray<RefPtr<Promise>> promises; + + for (nsINode* root : mRoots) { + RefPtr<Promise> promise = TranslateFragment(*root, aRv); + if (MOZ_UNLIKELY(aRv.Failed())) { + return nullptr; + } + + // If the root is an element, we'll add a native handler + // to set root info (language, direction etc.) on it + // once the localization finishes. + if (root->IsElement()) { + RefPtr<L10nRootTranslationHandler> nativeHandler = + new L10nRootTranslationHandler(root->AsElement()); + promise->AppendNativeHandler(nativeHandler); + } + + promises.AppendElement(promise); + } + AutoEntryScript aes(mGlobal, "DOMLocalization TranslateRoots"); + return Promise::All(aes.cx(), promises, aRv); +} + +/** + * Helper methods + */ + +/* static */ +void DOMLocalization::GetTranslatables( + nsINode& aNode, Sequence<OwningNonNull<Element>>& aElements, + ErrorResult& aRv) { + nsIContent* node = + aNode.IsContent() ? aNode.AsContent() : aNode.GetFirstChild(); + for (; node; node = node->GetNextNode(&aNode)) { + if (!node->IsElement()) { + continue; + } + + Element* domElement = node->AsElement(); + + if (!domElement->HasAttr(nsGkAtoms::datal10nid)) { + continue; + } + + if (!aElements.AppendElement(*domElement, fallible)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + } +} + +/* static */ +void DOMLocalization::SetRootInfo(Element* aElement) { + nsAutoCString primaryLocale; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(primaryLocale); + aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::lang, + NS_ConvertUTF8toUTF16(primaryLocale), true); + + nsAutoString dir; + if (LocaleService::GetInstance()->IsAppLocaleRTL()) { + nsGkAtoms::rtl->ToString(dir); + } else { + nsGkAtoms::ltr->ToString(dir); + } + + uint32_t nameSpace = aElement->GetNameSpaceID(); + nsAtom* dirAtom = + nameSpace == kNameSpaceID_XUL ? nsGkAtoms::localedir : nsGkAtoms::dir; + + aElement->SetAttr(kNameSpaceID_None, dirAtom, dir, true); +} + +bool DOMLocalization::ApplyTranslations( + nsTArray<nsCOMPtr<Element>>& aElements, + nsTArray<Nullable<L10nMessage>>& aTranslations, + nsXULPrototypeDocument* aProto, ErrorResult& aRv) { + if (aElements.Length() != aTranslations.Length()) { + aRv.Throw(NS_ERROR_FAILURE); + return false; + } + + PauseObserving(); + + bool hasMissingTranslation = false; + + nsTArray<L10nOverlaysError> errors; + for (size_t i = 0; i < aTranslations.Length(); ++i) { + nsCOMPtr elem = aElements[i]; + if (aTranslations[i].IsNull()) { + hasMissingTranslation = true; + continue; + } + // If we have a proto, we expect all elements are connected up. + // If they're not, they may have been removed by earlier translations. + // We will have added an error in L10nOverlays in this case. + // This is an error in fluent use, but shouldn't be crashing. There's + // also no point translating the element - skip it: + if (aProto && !elem->IsInComposedDoc()) { + continue; + } + + // It is possible that someone removed the `data-l10n-id` from the element + // before the async translation completed. In that case, skip applying + // the translation. + if (!elem->HasAttr(nsGkAtoms::datal10nid)) { + continue; + } + L10nOverlays::TranslateElement(*elem, aTranslations[i].Value(), errors, + aRv); + if (NS_WARN_IF(aRv.Failed())) { + hasMissingTranslation = true; + continue; + } + if (aProto) { + // We only need to rebuild deep if the translation has a value. + // Otherwise we'll only rebuild the attributes. + aProto->RebuildL10nPrototype(elem, + !aTranslations[i].Value().mValue.IsVoid()); + } + } + + ReportL10nOverlaysErrors(errors); + + ResumeObserving(); + + return !hasMissingTranslation; +} + +/* Protected */ + +void DOMLocalization::OnChange() { + Localization::OnChange(); + RefPtr<Promise> promise = TranslateRoots(IgnoreErrors()); +} + +void DOMLocalization::DisconnectMutations() { + if (mMutations) { + mMutations->Disconnect(); + DisconnectRoots(); + } +} + +void DOMLocalization::DisconnectRoots() { + for (nsINode* node : mRoots) { + node->RemoveMutationObserver(mMutations); + } + mRoots.Clear(); +} + +void DOMLocalization::ReportL10nOverlaysErrors( + nsTArray<L10nOverlaysError>& aErrors) { + nsAutoString msg; + + for (auto& error : aErrors) { + if (error.mCode.WasPassed()) { + msg = u"[fluent-dom] "_ns; + switch (error.mCode.Value()) { + case L10nOverlays_Binding::ERROR_FORBIDDEN_TYPE: + msg += u"An element of forbidden type \""_ns + + error.mTranslatedElementName.Value() + + nsLiteralString( + u"\" was found in the translation. Only safe text-level " + "elements and elements with data-l10n-name are allowed."); + break; + case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_MISSING: + msg += u"An element named \""_ns + error.mL10nName.Value() + + u"\" wasn't found in the source."_ns; + break; + case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_TYPE_MISMATCH: + msg += u"An element named \""_ns + error.mL10nName.Value() + + nsLiteralString( + u"\" was found in the translation but its type ") + + error.mTranslatedElementName.Value() + + nsLiteralString( + u" didn't match the element found in the source ") + + error.mSourceElementName.Value() + u"."_ns; + break; + case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISCONNECTED: + msg += u"The element using message \""_ns + error.mL10nName.Value() + + nsLiteralString( + u"\" was removed from the DOM when translating its \"") + + error.mTranslatedElementName.Value() + u"\" parent."_ns; + break; + case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISALLOWED_DOM: + msg += nsLiteralString( + u"While translating an element with fluent ID \"") + + error.mL10nName.Value() + u"\" a child element of type \""_ns + + error.mTranslatedElementName.Value() + + nsLiteralString( + u"\" was removed. Either the fluent message " + "does not contain markup, or it does not contain markup " + "of this type."); + break; + case L10nOverlays_Binding::ERROR_UNKNOWN: + default: + msg += nsLiteralString( + u"Unknown error happened while translating an element."); + break; + } + nsPIDOMWindowInner* innerWindow = GetParentObject()->GetAsInnerWindow(); + Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr; + if (doc) { + nsContentUtils::ReportToConsoleNonLocalized( + msg, nsIScriptError::warningFlag, "DOM"_ns, doc); + } else { + NS_WARNING("Failed to report l10n DOM Overlay errors to console."); + } + printf_stderr("%s\n", NS_ConvertUTF16toUTF8(msg).get()); + } + } +} + +void DOMLocalization::ConvertStringToL10nArgs(const nsString& aInput, + intl::L10nArgs& aRetVal, + ErrorResult& aRv) { + if (aInput.IsEmpty()) { + // There are no properties. + return; + } + + Json::Value args; + Json::Reader jsonReader; + + if (!jsonReader.parse(NS_ConvertUTF16toUTF8(aInput).get(), args, false)) { + nsTArray<nsCString> errors{ + "[dom/l10n] Failed to parse l10n-args JSON: "_ns + + NS_ConvertUTF16toUTF8(aInput), + }; + MaybeReportErrorsToGecko(errors, aRv, GetParentObject()); + return; + } + + if (!args.isObject()) { + nsTArray<nsCString> errors{ + "[dom/l10n] Failed to parse l10n-args JSON: "_ns + + NS_ConvertUTF16toUTF8(aInput), + }; + MaybeReportErrorsToGecko(errors, aRv, GetParentObject()); + return; + } + + for (Json::ValueConstIterator iter = args.begin(); iter != args.end(); + ++iter) { + L10nArgs::EntryType* newEntry = aRetVal.Entries().AppendElement(fallible); + if (!newEntry) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + newEntry->mKey = iter.name().c_str(); + if (iter->isString()) { + newEntry->mValue.SetValue().RawSetAsUTF8String().Assign( + iter->asString().c_str(), iter->asString().length()); + } else if (iter->isDouble()) { + newEntry->mValue.SetValue().RawSetAsDouble() = iter->asDouble(); + } else if (iter->isBool()) { + if (iter->asBool()) { + newEntry->mValue.SetValue().RawSetAsUTF8String().Assign("true"); + } else { + newEntry->mValue.SetValue().RawSetAsUTF8String().Assign("false"); + } + } else if (iter->isNull()) { + newEntry->mValue.SetNull(); + } else { + nsTArray<nsCString> errors{ + "[dom/l10n] Failed to convert l10n-args JSON: "_ns + + NS_ConvertUTF16toUTF8(aInput), + }; + MaybeReportErrorsToGecko(errors, aRv, GetParentObject()); + } + } +} |