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 }