tor-browser

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

DOMLocalization.cpp (23641B)


      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 "DOMLocalization.h"
      8 
      9 #include "js/ForOfIterator.h"  // JS::ForOfIterator
     10 #include "json/json.h"
     11 #include "mozilla/dom/AutoEntryScript.h"
     12 #include "mozilla/dom/Element.h"
     13 #include "mozilla/dom/L10nOverlays.h"
     14 #include "mozilla/intl/L10nRegistry.h"
     15 #include "mozilla/intl/LocaleService.h"
     16 #include "nsContentUtils.h"
     17 #include "nsIScriptError.h"
     18 
     19 using namespace mozilla;
     20 using namespace mozilla::dom;
     21 using namespace mozilla::intl;
     22 
     23 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMLocalization)
     24 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMLocalization, Localization)
     25  tmp->DisconnectMutations();
     26  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMutations)
     27  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoots)
     28  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     29 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMLocalization, Localization)
     31  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMutations)
     32  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoots)
     33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     34 
     35 NS_IMPL_ADDREF_INHERITED(DOMLocalization, Localization)
     36 NS_IMPL_RELEASE_INHERITED(DOMLocalization, Localization)
     37 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMLocalization)
     38 NS_INTERFACE_MAP_END_INHERITING(Localization)
     39 
     40 DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, bool aSync)
     41    : Localization(aGlobal, aSync) {
     42  mMutations = new L10nMutations(this);
     43 }
     44 
     45 DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, bool aIsSync,
     46                                 const ffi::LocalizationRc* aRaw)
     47    : Localization(aGlobal, aIsSync, aRaw) {
     48  mMutations = new L10nMutations(this);
     49 }
     50 
     51 DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, bool aIsSync,
     52                                 const nsTArray<nsCString>& aLocales)
     53    : Localization(aGlobal, aIsSync, aLocales) {
     54  mMutations = new L10nMutations(this);
     55 }
     56 
     57 already_AddRefed<DOMLocalization> DOMLocalization::Constructor(
     58    const GlobalObject& aGlobal,
     59    const Sequence<dom::OwningUTF8StringOrResourceId>& aResourceIds,
     60    bool aIsSync, const Optional<NonNull<L10nRegistry>>& aRegistry,
     61    const Optional<Sequence<nsCString>>& aLocales, ErrorResult& aRv) {
     62  auto ffiResourceIds{L10nRegistry::ResourceIdsToFFI(aResourceIds)};
     63  Maybe<nsTArray<nsCString>> locales;
     64 
     65  if (aLocales.WasPassed()) {
     66    locales.emplace();
     67    locales->SetCapacity(aLocales.Value().Length());
     68    for (const auto& locale : aLocales.Value()) {
     69      locales->AppendElement(locale);
     70    }
     71  }
     72 
     73  RefPtr<const ffi::LocalizationRc> raw;
     74  bool result;
     75 
     76  if (aRegistry.WasPassed()) {
     77    result = ffi::localization_new_with_locales(
     78        &ffiResourceIds, aIsSync, aRegistry.Value().Raw(),
     79        locales.ptrOr(nullptr), getter_AddRefs(raw));
     80  } else {
     81    result = ffi::localization_new_with_locales(&ffiResourceIds, aIsSync,
     82                                                nullptr, locales.ptrOr(nullptr),
     83                                                getter_AddRefs(raw));
     84  }
     85 
     86  if (result) {
     87    nsCOMPtr<nsIGlobalObject> global =
     88        do_QueryInterface(aGlobal.GetAsSupports());
     89 
     90    return do_AddRef(new DOMLocalization(global, aIsSync, raw));
     91  }
     92  aRv.ThrowInvalidStateError(
     93      "Failed to create the Localization. Check the locales arguments.");
     94  return nullptr;
     95 }
     96 
     97 JSObject* DOMLocalization::WrapObject(JSContext* aCx,
     98                                      JS::Handle<JSObject*> aGivenProto) {
     99  return DOMLocalization_Binding::Wrap(aCx, this, aGivenProto);
    100 }
    101 
    102 void DOMLocalization::Destroy() { DisconnectMutations(); }
    103 
    104 DOMLocalization::~DOMLocalization() { Destroy(); }
    105 
    106 bool DOMLocalization::HasPendingMutations() const {
    107  return mMutations && mMutations->HasPendingMutations();
    108 }
    109 
    110 /**
    111 * DOMLocalization API
    112 */
    113 
    114 void DOMLocalization::ConnectRoot(nsINode& aNode) {
    115  nsCOMPtr<nsIGlobalObject> global = aNode.GetOwnerGlobal();
    116  if (!global) {
    117    return;
    118  }
    119  MOZ_ASSERT(global == mGlobal,
    120             "Cannot add a root that overlaps with existing root.");
    121 
    122 #ifdef DEBUG
    123  for (nsINode* root : mRoots) {
    124    MOZ_ASSERT(
    125        root != &aNode && !root->Contains(&aNode) && !aNode.Contains(root),
    126        "Cannot add a root that overlaps with existing root.");
    127  }
    128 #endif
    129 
    130  mRoots.Insert(&aNode);
    131 
    132  aNode.AddMutationObserverUnlessExists(mMutations);
    133 }
    134 
    135 void DOMLocalization::DisconnectRoot(nsINode& aNode) {
    136  if (mRoots.Contains(&aNode)) {
    137    aNode.RemoveMutationObserver(mMutations);
    138    mRoots.Remove(&aNode);
    139  }
    140 }
    141 
    142 void DOMLocalization::PauseObserving() { mMutations->PauseObserving(); }
    143 
    144 void DOMLocalization::ResumeObserving() { mMutations->ResumeObserving(); }
    145 
    146 void DOMLocalization::SetAttributes(
    147    JSContext* aCx, Element& aElement, const nsAString& aId,
    148    const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv) {
    149  if (aArgs.WasPassed() && aArgs.Value()) {
    150    nsAutoString data;
    151    JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value()));
    152    if (!nsContentUtils::StringifyJSON(aCx, val, data,
    153                                       UndefinedIsNullStringLiteral)) {
    154      aRv.NoteJSContextException(aCx);
    155      return;
    156    }
    157    if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nargs, data,
    158                              eCaseMatters)) {
    159      aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, true);
    160    }
    161  } else {
    162    aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, true);
    163  }
    164 
    165  if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nid, aId,
    166                            eCaseMatters)) {
    167    aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, aId, true);
    168  }
    169 }
    170 
    171 void DOMLocalization::GetAttributes(Element& aElement, L10nIdArgs& aResult,
    172                                    ErrorResult& aRv) {
    173  nsAutoString l10nId;
    174  nsAutoString l10nArgs;
    175 
    176  if (aElement.GetAttr(nsGkAtoms::datal10nid, l10nId)) {
    177    CopyUTF16toUTF8(l10nId, aResult.mId);
    178  }
    179 
    180  if (aElement.GetAttr(nsGkAtoms::datal10nargs, l10nArgs)) {
    181    ConvertStringToL10nArgs(aResult.mId, l10nArgs, aResult.mArgs.SetValue(),
    182                            aRv);
    183  }
    184 }
    185 
    186 void DOMLocalization::SetArgs(JSContext* aCx, Element& aElement,
    187                              const Optional<JS::Handle<JSObject*>>& aArgs,
    188                              ErrorResult& aRv) {
    189  if (aArgs.WasPassed() && aArgs.Value()) {
    190    nsAutoString data;
    191    JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value()));
    192    if (!nsContentUtils::StringifyJSON(aCx, val, data,
    193                                       UndefinedIsNullStringLiteral)) {
    194      aRv.NoteJSContextException(aCx);
    195      return;
    196    }
    197    if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nargs, data,
    198                              eCaseMatters)) {
    199      aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, data, true);
    200    }
    201  } else {
    202    aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, true);
    203  }
    204 }
    205 
    206 already_AddRefed<Promise> DOMLocalization::TranslateFragment(nsINode& aNode,
    207                                                             ErrorResult& aRv) {
    208  Sequence<OwningNonNull<Element>> elements;
    209  GetTranslatables(aNode, elements, aRv);
    210  if (NS_WARN_IF(aRv.Failed())) {
    211    return nullptr;
    212  }
    213  return TranslateElements(elements, aRv);
    214 }
    215 
    216 /**
    217 * A Promise Handler used to apply the result of
    218 * a call to Localization::FormatMessages onto the list
    219 * of translatable elements.
    220 */
    221 class ElementTranslationHandler : public PromiseNativeHandler {
    222 public:
    223  explicit ElementTranslationHandler(DOMLocalization* aDOMLocalization,
    224                                     nsXULPrototypeDocument* aProto)
    225      : mDOMLocalization(aDOMLocalization), mProto(aProto) {};
    226 
    227  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    228  NS_DECL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
    229 
    230  nsTArray<nsCOMPtr<Element>>& Elements() { return mElements; }
    231 
    232  void SetReturnValuePromise(Promise* aReturnValuePromise) {
    233    mReturnValuePromise = aReturnValuePromise;
    234  }
    235 
    236  virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
    237                                ErrorResult& aRv) override {
    238    ErrorResult rv;
    239 
    240    nsTArray<Nullable<L10nMessage>> l10nData;
    241    if (aValue.isObject()) {
    242      JS::ForOfIterator iter(aCx);
    243      if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
    244        mReturnValuePromise->MaybeRejectWithUndefined();
    245        return;
    246      }
    247      if (!iter.valueIsIterable()) {
    248        mReturnValuePromise->MaybeRejectWithUndefined();
    249        return;
    250      }
    251 
    252      JS::Rooted<JS::Value> temp(aCx);
    253      while (true) {
    254        bool done;
    255        if (!iter.next(&temp, &done)) {
    256          mReturnValuePromise->MaybeRejectWithUndefined();
    257          return;
    258        }
    259 
    260        if (done) {
    261          break;
    262        }
    263 
    264        Nullable<L10nMessage>* slotPtr =
    265            l10nData.AppendElement(mozilla::fallible);
    266        if (!slotPtr) {
    267          mReturnValuePromise->MaybeRejectWithUndefined();
    268          return;
    269        }
    270 
    271        if (!temp.isNull()) {
    272          if (!slotPtr->SetValue().Init(aCx, temp)) {
    273            mReturnValuePromise->MaybeRejectWithUndefined();
    274            return;
    275          }
    276        }
    277      }
    278    }
    279 
    280    bool allTranslated =
    281        mDOMLocalization->ApplyTranslations(mElements, l10nData, mProto, rv);
    282    if (NS_WARN_IF(rv.Failed()) || !allTranslated) {
    283      mReturnValuePromise->MaybeRejectWithUndefined();
    284      return;
    285    }
    286 
    287    mReturnValuePromise->MaybeResolveWithUndefined();
    288  }
    289 
    290  virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
    291                                ErrorResult& aRv) override {
    292    mReturnValuePromise->MaybeRejectWithClone(aCx, aValue);
    293  }
    294 
    295 private:
    296  ~ElementTranslationHandler() = default;
    297 
    298  nsTArray<nsCOMPtr<Element>> mElements;
    299  RefPtr<DOMLocalization> mDOMLocalization;
    300  RefPtr<Promise> mReturnValuePromise;
    301  RefPtr<nsXULPrototypeDocument> mProto;
    302 };
    303 
    304 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementTranslationHandler)
    305  NS_INTERFACE_MAP_ENTRY(nsISupports)
    306 NS_INTERFACE_MAP_END
    307 
    308 NS_IMPL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
    309 
    310 NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementTranslationHandler)
    311 NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementTranslationHandler)
    312 
    313 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementTranslationHandler)
    314  NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
    315  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMLocalization)
    316  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValuePromise)
    317  NS_IMPL_CYCLE_COLLECTION_UNLINK(mProto)
    318 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    319 
    320 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementTranslationHandler)
    321  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
    322  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMLocalization)
    323  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValuePromise)
    324  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProto)
    325 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    326 
    327 already_AddRefed<Promise> DOMLocalization::TranslateElements(
    328    const nsTArray<OwningNonNull<Element>>& aElements, ErrorResult& aRv) {
    329  return TranslateElements(aElements, nullptr, aRv);
    330 }
    331 
    332 already_AddRefed<Promise> DOMLocalization::TranslateElements(
    333    const nsTArray<OwningNonNull<Element>>& aElements,
    334    nsXULPrototypeDocument* aProto, ErrorResult& aRv) {
    335  Sequence<OwningUTF8StringOrL10nIdArgs> l10nKeys;
    336  RefPtr<ElementTranslationHandler> nativeHandler =
    337      new ElementTranslationHandler(this, aProto);
    338  nsTArray<nsCOMPtr<Element>>& domElements = nativeHandler->Elements();
    339  domElements.SetCapacity(aElements.Length());
    340 
    341  if (!mGlobal) {
    342    aRv.Throw(NS_ERROR_UNEXPECTED);
    343    return nullptr;
    344  }
    345 
    346  for (auto& domElement : aElements) {
    347    if (!domElement->HasAttr(nsGkAtoms::datal10nid)) {
    348      continue;
    349    }
    350 
    351    OwningUTF8StringOrL10nIdArgs* key = l10nKeys.AppendElement(fallible);
    352    if (!key) {
    353      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    354      return nullptr;
    355    }
    356 
    357    GetAttributes(*domElement, key->SetAsL10nIdArgs(), aRv);
    358    if (NS_WARN_IF(aRv.Failed())) {
    359      return nullptr;
    360    }
    361 
    362    if (!domElements.AppendElement(domElement, fallible)) {
    363      // This can't really happen, we SetCapacity'd above...
    364      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    365      return nullptr;
    366    }
    367  }
    368 
    369  RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
    370  if (NS_WARN_IF(aRv.Failed())) {
    371    return nullptr;
    372  }
    373 
    374  if (IsSync()) {
    375    nsTArray<Nullable<L10nMessage>> l10nMessages;
    376 
    377    FormatMessagesSync(l10nKeys, l10nMessages, aRv);
    378 
    379    if (NS_WARN_IF(aRv.Failed())) {
    380      promise->MaybeRejectWithUndefined();
    381      return promise.forget();
    382    }
    383 
    384    bool allTranslated =
    385        ApplyTranslations(domElements, l10nMessages, aProto, aRv);
    386    if (NS_WARN_IF(aRv.Failed()) || !allTranslated) {
    387      promise->MaybeRejectWithUndefined();
    388      return promise.forget();
    389    }
    390 
    391    promise->MaybeResolveWithUndefined();
    392    return promise.forget();
    393  }
    394  RefPtr<Promise> callbackResult = FormatMessages(l10nKeys, aRv);
    395  if (NS_WARN_IF(aRv.Failed())) {
    396    return nullptr;
    397  }
    398  nativeHandler->SetReturnValuePromise(promise);
    399  callbackResult->AppendNativeHandler(nativeHandler);
    400  return MaybeWrapPromise(promise);
    401 }
    402 
    403 /**
    404 * Promise handler used to set localization data on
    405 * roots of elements that got successfully translated.
    406 */
    407 class L10nRootTranslationHandler final : public PromiseNativeHandler {
    408 public:
    409  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    410  NS_DECL_CYCLE_COLLECTION_CLASS(L10nRootTranslationHandler)
    411 
    412  explicit L10nRootTranslationHandler(Element* aRoot) : mRoot(aRoot) {}
    413 
    414  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
    415                        ErrorResult& aRv) override {
    416    DOMLocalization::SetRootInfo(mRoot);
    417  }
    418 
    419  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
    420                        ErrorResult& aRv) override {}
    421 
    422 private:
    423  ~L10nRootTranslationHandler() = default;
    424 
    425  RefPtr<Element> mRoot;
    426 };
    427 
    428 NS_IMPL_CYCLE_COLLECTION(L10nRootTranslationHandler, mRoot)
    429 
    430 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nRootTranslationHandler)
    431  NS_INTERFACE_MAP_ENTRY(nsISupports)
    432 NS_INTERFACE_MAP_END
    433 
    434 NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nRootTranslationHandler)
    435 NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nRootTranslationHandler)
    436 
    437 already_AddRefed<Promise> DOMLocalization::TranslateRoots(ErrorResult& aRv) {
    438  nsTArray<RefPtr<Promise>> promises;
    439 
    440  for (nsINode* root : mRoots) {
    441    RefPtr<Promise> promise = TranslateFragment(*root, aRv);
    442    if (MOZ_UNLIKELY(aRv.Failed())) {
    443      return nullptr;
    444    }
    445 
    446    // If the root is an element, we'll add a native handler
    447    // to set root info (language, direction etc.) on it
    448    // once the localization finishes.
    449    if (root->IsElement()) {
    450      RefPtr<L10nRootTranslationHandler> nativeHandler =
    451          new L10nRootTranslationHandler(root->AsElement());
    452      promise->AppendNativeHandler(nativeHandler);
    453    }
    454 
    455    promises.AppendElement(promise);
    456  }
    457  AutoEntryScript aes(mGlobal, "DOMLocalization TranslateRoots");
    458  return Promise::All(aes.cx(), promises, aRv);
    459 }
    460 
    461 /**
    462 * Helper methods
    463 */
    464 
    465 /* static */
    466 void DOMLocalization::GetTranslatables(
    467    nsINode& aNode, Sequence<OwningNonNull<Element>>& aElements,
    468    ErrorResult& aRv) {
    469  nsIContent* node =
    470      aNode.IsContent() ? aNode.AsContent() : aNode.GetFirstChild();
    471  for (; node; node = node->GetNextNode(&aNode)) {
    472    if (!node->IsElement()) {
    473      continue;
    474    }
    475 
    476    Element* domElement = node->AsElement();
    477 
    478    if (!domElement->HasAttr(nsGkAtoms::datal10nid)) {
    479      continue;
    480    }
    481 
    482    if (!aElements.AppendElement(*domElement, fallible)) {
    483      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    484      return;
    485    }
    486  }
    487 }
    488 
    489 /* static */
    490 void DOMLocalization::SetRootInfo(Element* aElement) {
    491  nsAutoCString primaryLocale;
    492  LocaleService::GetInstance()->GetAppLocaleAsBCP47(primaryLocale);
    493  aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::lang,
    494                    NS_ConvertUTF8toUTF16(primaryLocale), true);
    495 
    496  nsAutoString dir;
    497  if (LocaleService::GetInstance()->IsAppLocaleRTL()) {
    498    nsGkAtoms::rtl->ToString(dir);
    499  } else {
    500    nsGkAtoms::ltr->ToString(dir);
    501  }
    502 
    503  uint32_t nameSpace = aElement->GetNameSpaceID();
    504  nsAtom* dirAtom =
    505      nameSpace == kNameSpaceID_XUL ? nsGkAtoms::localedir : nsGkAtoms::dir;
    506 
    507  aElement->SetAttr(kNameSpaceID_None, dirAtom, dir, true);
    508 }
    509 
    510 bool DOMLocalization::ApplyTranslations(
    511    nsTArray<nsCOMPtr<Element>>& aElements,
    512    nsTArray<Nullable<L10nMessage>>& aTranslations,
    513    nsXULPrototypeDocument* aProto, ErrorResult& aRv) {
    514  if (aElements.Length() != aTranslations.Length()) {
    515    aRv.Throw(NS_ERROR_FAILURE);
    516    return false;
    517  }
    518 
    519  PauseObserving();
    520 
    521  bool hasMissingTranslation = false;
    522 
    523  nsTArray<L10nOverlaysError> errors;
    524  for (size_t i = 0; i < aTranslations.Length(); ++i) {
    525    nsCOMPtr elem = aElements[i];
    526    if (aTranslations[i].IsNull()) {
    527      hasMissingTranslation = true;
    528      continue;
    529    }
    530    // If we have a proto, we expect all elements are connected up.
    531    // If they're not, they may have been removed by earlier translations.
    532    // We will have added an error in L10nOverlays in this case.
    533    // This is an error in fluent use, but shouldn't be crashing. There's
    534    // also no point translating the element - skip it:
    535    if (aProto && !elem->IsInComposedDoc()) {
    536      continue;
    537    }
    538 
    539    // It is possible that someone removed the `data-l10n-id` from the element
    540    // before the async translation completed. In that case, skip applying
    541    // the translation.
    542    if (!elem->HasAttr(nsGkAtoms::datal10nid)) {
    543      continue;
    544    }
    545    L10nOverlays::TranslateElement(*elem, aTranslations[i].Value(), errors,
    546                                   aRv);
    547    if (NS_WARN_IF(aRv.Failed())) {
    548      hasMissingTranslation = true;
    549      continue;
    550    }
    551    if (aProto) {
    552      // We only need to rebuild deep if the translation has a value.
    553      // Otherwise we'll only rebuild the attributes.
    554      aProto->RebuildL10nPrototype(elem,
    555                                   !aTranslations[i].Value().mValue.IsVoid());
    556    }
    557  }
    558 
    559  ReportL10nOverlaysErrors(errors);
    560 
    561  ResumeObserving();
    562 
    563  return !hasMissingTranslation;
    564 }
    565 
    566 /* Protected */
    567 
    568 void DOMLocalization::OnChange() {
    569  Localization::OnChange();
    570  RefPtr<Promise> promise = TranslateRoots(IgnoreErrors());
    571 }
    572 
    573 void DOMLocalization::DisconnectMutations() {
    574  if (mMutations) {
    575    mMutations->Disconnect();
    576    DisconnectRoots();
    577  }
    578 }
    579 
    580 void DOMLocalization::DisconnectRoots() {
    581  for (nsINode* node : mRoots) {
    582    node->RemoveMutationObserver(mMutations);
    583  }
    584  mRoots.Clear();
    585 }
    586 
    587 void DOMLocalization::ReportL10nOverlaysErrors(
    588    nsTArray<L10nOverlaysError>& aErrors) {
    589  nsAutoString msg;
    590 
    591  for (auto& error : aErrors) {
    592    if (error.mCode.WasPassed()) {
    593      msg = u"[fluent-dom] "_ns;
    594      switch (error.mCode.Value()) {
    595        case L10nOverlays_Binding::ERROR_FORBIDDEN_TYPE:
    596          msg += u"An element of forbidden type \""_ns +
    597                 error.mTranslatedElementName.Value() +
    598                 nsLiteralString(
    599                     u"\" was found in the translation. Only safe text-level "
    600                     "elements and elements with data-l10n-name are allowed.");
    601          break;
    602        case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_MISSING:
    603          msg += u"An element named \""_ns + error.mL10nName.Value() +
    604                 u"\" wasn't found in the source."_ns;
    605          break;
    606        case L10nOverlays_Binding::ERROR_NAMED_ELEMENT_TYPE_MISMATCH:
    607          msg += u"An element named \""_ns + error.mL10nName.Value() +
    608                 nsLiteralString(
    609                     u"\" was found in the translation but its type ") +
    610                 error.mTranslatedElementName.Value() +
    611                 nsLiteralString(
    612                     u" didn't match the element found in the source ") +
    613                 error.mSourceElementName.Value() + u"."_ns;
    614          break;
    615        case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISCONNECTED:
    616          msg += u"The element using message \""_ns + error.mL10nName.Value() +
    617                 nsLiteralString(
    618                     u"\" was removed from the DOM when translating its \"") +
    619                 error.mTranslatedElementName.Value() + u"\" parent."_ns;
    620          break;
    621        case L10nOverlays_Binding::ERROR_TRANSLATED_ELEMENT_DISALLOWED_DOM:
    622          msg += nsLiteralString(
    623                     u"While translating an element with fluent ID \"") +
    624                 error.mL10nName.Value() + u"\" a child element of type \""_ns +
    625                 error.mTranslatedElementName.Value() +
    626                 nsLiteralString(
    627                     u"\" was removed. Either the fluent message "
    628                     "does not contain markup, or it does not contain markup "
    629                     "of this type.");
    630          break;
    631        case L10nOverlays_Binding::ERROR_UNKNOWN:
    632        default:
    633          msg += nsLiteralString(
    634              u"Unknown error happened while translating an element.");
    635          break;
    636      }
    637      nsPIDOMWindowInner* innerWindow = GetParentObject()->GetAsInnerWindow();
    638      Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
    639      if (doc) {
    640        nsContentUtils::ReportToConsoleNonLocalized(
    641            msg, nsIScriptError::warningFlag, "DOM"_ns, doc);
    642      } else {
    643        NS_WARNING("Failed to report l10n DOM Overlay errors to console.");
    644      }
    645      printf_stderr("%s\n", NS_ConvertUTF16toUTF8(msg).get());
    646    }
    647  }
    648 }
    649 
    650 void DOMLocalization::ConvertStringToL10nArgs(const nsCString& aL10nId,
    651                                              const nsString& aInput,
    652                                              intl::L10nArgs& aRetVal,
    653                                              ErrorResult& aRv) {
    654  if (aInput.IsEmpty()) {
    655    // There are no properties.
    656    return;
    657  }
    658 
    659  Json::Value args;
    660  Json::Reader jsonReader;
    661 
    662  if (!jsonReader.parse(NS_ConvertUTF16toUTF8(aInput).get(), args, false)) {
    663    nsTArray<nsCString> errors{
    664        "[dom/l10n] Failed to parse l10n-args JSON ("_ns + aL10nId + "): "_ns +
    665            NS_ConvertUTF16toUTF8(aInput),
    666    };
    667    MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
    668    return;
    669  }
    670 
    671  if (!args.isObject()) {
    672    nsTArray<nsCString> errors{
    673        "[dom/l10n] Failed to parse l10n-args as JSON object ("_ns + aL10nId +
    674            "): "_ns + NS_ConvertUTF16toUTF8(aInput),
    675    };
    676    MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
    677    return;
    678  }
    679 
    680  for (Json::ValueConstIterator iter = args.begin(); iter != args.end();
    681       ++iter) {
    682    L10nArgs::EntryType* newEntry = aRetVal.Entries().AppendElement(fallible);
    683    if (!newEntry) {
    684      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    685      return;
    686    }
    687    newEntry->mKey = iter.name().c_str();
    688    if (iter->isString()) {
    689      newEntry->mValue.SetValue().RawSetAsUTF8String().Assign(
    690          iter->asString().c_str(), iter->asString().length());
    691    } else if (iter->isDouble()) {
    692      newEntry->mValue.SetValue().RawSetAsDouble() = iter->asDouble();
    693    } else if (iter->isBool()) {
    694      if (iter->asBool()) {
    695        newEntry->mValue.SetValue().RawSetAsUTF8String().Assign("true");
    696      } else {
    697        newEntry->mValue.SetValue().RawSetAsUTF8String().Assign("false");
    698      }
    699    } else if (iter->isNull()) {
    700      newEntry->mValue.SetNull();
    701    } else {
    702      nsTArray<nsCString> errors{
    703          "[dom/l10n] Failed to convert l10n-args JSON ("_ns + aL10nId +
    704              "): "_ns + NS_ConvertUTF16toUTF8(aInput),
    705      };
    706      MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
    707    }
    708  }
    709 }