Bug 1613705 - [localization] part2: Switch Localization class to use localization-ffi. r=emilio,nika

Depends on D104788

Differential Revision: https://phabricator.services.mozilla.com/D104789
This commit is contained in:
Zibi Braniecki 2021-08-03 16:25:10 +00:00
parent 05db766a87
commit 9f3aa2521f
17 changed files with 362 additions and 509 deletions

View File

@ -712,7 +712,7 @@ size_t IdentifierMapEntry::SizeOfExcludingThis(
class SubDocMapEntry : public PLDHashEntryHdr {
public:
// Both of these are strong references
Element* mKey; // must be first, to look like PLDHashEntryStub
dom::Element* mKey; // must be first, to look like PLDHashEntryStub
dom::Document* mSubDocument;
};
@ -4140,18 +4140,6 @@ bool Document::GetAllowPlugins() {
return true;
}
void Document::EnsureL10n() {
if (!mDocumentL10n) {
Element* elem = GetDocumentElement();
if (NS_WARN_IF(!elem)) {
return;
}
bool isSync = elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nsync);
mDocumentL10n = DocumentL10n::Create(this, isSync);
MOZ_ASSERT(mDocumentL10n);
}
}
bool Document::HasPendingInitialTranslation() {
return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
}
@ -4174,15 +4162,20 @@ void Document::LocalizationLinkAdded(Element* aLinkElement) {
return;
}
EnsureL10n();
nsAutoString href;
aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
if (!mDocumentL10n) {
Element* elem = GetDocumentElement();
MOZ_DIAGNOSTIC_ASSERT(elem);
bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
mDocumentL10n = DocumentL10n::Create(this, isSync);
MOZ_ASSERT(mDocumentL10n);
}
mDocumentL10n->AddResourceId(href);
if (mReadyState >= READYSTATE_INTERACTIVE) {
mDocumentL10n->Activate(true);
mDocumentL10n->TriggerInitialTranslation();
} else {
if (!mDocumentL10n->mBlockingLayout) {
@ -4227,9 +4220,8 @@ void Document::LocalizationLinkRemoved(Element* aLinkElement) {
* collected.
*/
void Document::OnL10nResourceContainerParsed() {
if (mDocumentL10n) {
mDocumentL10n->Activate(false);
}
// XXX: This is a scaffolding for where we might inject prefetch
// in bug 1717241.
}
void Document::OnParsingCompleted() {

View File

@ -3854,8 +3854,6 @@ class Document : public nsINode,
private:
bool IsErrorPage() const;
void EnsureL10n();
// Takes the bits from mStyleUseCounters if appropriate, and sets them in
// mUseCounters.
void SetCssUseCounterBits();

View File

@ -465,7 +465,6 @@ DOMInterfaces = {
},
'Localization': {
'implicitJSContext': [ 'formatValue', 'formatValues', 'formatMessages', 'formatValueSync', 'formatValuesSync', 'formatMessagesSync' ],
'nativeType': 'mozilla::intl::Localization',
},

View File

@ -435,6 +435,17 @@ template <typename K, typename V>
return true;
}
template <typename T>
[[nodiscard]] bool ToJSValue(JSContext* aCx, const Nullable<T>& aArgument,
JS::MutableHandle<JS::Value> aValue) {
if (aArgument.IsNull()) {
aValue.setNull();
return true;
}
return ToJSValue(aCx, aArgument.Value(), aValue);
}
} // namespace dom
} // namespace mozilla

View File

@ -42,14 +42,12 @@ already_AddRefed<DOMLocalization> DOMLocalization::Create(
RefPtr<DOMLocalization> domLoc =
new DOMLocalization(aGlobal, aSync, aBundleGenerator);
domLoc->Init();
return domLoc.forget();
}
DOMLocalization::DOMLocalization(nsIGlobalObject* aGlobal, const bool aSync,
const BundleGenerator& aBundleGenerator)
: Localization(aGlobal, aSync, aBundleGenerator) {
: Localization(aGlobal, aSync) {
mMutations = new L10nMutations(this);
}
@ -70,8 +68,6 @@ already_AddRefed<DOMLocalization> DOMLocalization::Constructor(
domLoc->AddResourceIds(aResourceIds);
}
domLoc->Activate(true);
return domLoc.forget();
}
@ -302,9 +298,6 @@ already_AddRefed<Promise> DOMLocalization::TranslateElements(
return nullptr;
}
AutoEntryScript aes(mGlobal, "DOMLocalization TranslateElements");
JSContext* cx = aes.cx();
for (auto& domElement : aElements) {
if (!domElement->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
continue;
@ -335,7 +328,7 @@ already_AddRefed<Promise> DOMLocalization::TranslateElements(
if (mIsSync) {
nsTArray<Nullable<L10nMessage>> l10nMessages;
FormatMessagesSync(cx, l10nKeys, l10nMessages, aRv);
FormatMessagesSync(l10nKeys, l10nMessages, aRv);
bool allTranslated =
ApplyTranslations(domElements, l10nMessages, aProto, aRv);
@ -346,7 +339,7 @@ already_AddRefed<Promise> DOMLocalization::TranslateElements(
promise->MaybeResolveWithUndefined();
} else {
RefPtr<Promise> callbackResult = FormatMessages(cx, l10nKeys, aRv);
RefPtr<Promise> callbackResult = FormatMessages(l10nKeys, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -528,10 +521,7 @@ bool DOMLocalization::ApplyTranslations(
void DOMLocalization::OnChange() {
Localization::OnChange();
if (mLocalization && !mResourceIds.IsEmpty()) {
ErrorResult rv;
RefPtr<Promise> promise = TranslateRoots(rv);
}
RefPtr<Promise> promise = TranslateRoots(IgnoreErrors());
}
void DOMLocalization::DisconnectMutations() {

View File

@ -14,6 +14,7 @@
#include "mozilla/dom/L10nMutations.h"
#include "mozilla/dom/L10nOverlaysBinding.h"
#include "mozilla/dom/LocalizationBinding.h"
#include "mozilla/dom/PromiseNativeHandler.h"
// XXX Avoid including this here by moving function bodies to the cpp file
#include "nsINode.h"

View File

@ -35,33 +35,25 @@ NS_INTERFACE_MAP_END_INHERITING(DOMLocalization)
bool DocumentL10n::mIsFirstBrowserWindow = true;
/* static */
RefPtr<DocumentL10n> DocumentL10n::Create(Document* aDocument,
const bool aSync) {
RefPtr<DocumentL10n> DocumentL10n::Create(Document* aDocument, bool aSync) {
RefPtr<DocumentL10n> l10n = new DocumentL10n(aDocument, aSync);
if (!l10n->Init()) {
IgnoredErrorResult rv;
l10n->mReady = Promise::Create(l10n->mGlobal, rv);
if (NS_WARN_IF(rv.Failed())) {
return nullptr;
}
return l10n.forget();
}
DocumentL10n::DocumentL10n(Document* aDocument, const bool aSync)
DocumentL10n::DocumentL10n(Document* aDocument, bool aSync)
: DOMLocalization(aDocument->GetScopeObject(), aSync, {}),
mDocument(aDocument),
mState(DocumentL10nState::Constructed) {
mContentSink = do_QueryInterface(aDocument->GetCurrentContentSink());
}
bool DocumentL10n::Init() {
DOMLocalization::Init();
ErrorResult rv;
mReady = Promise::Create(mGlobal, rv);
if (NS_WARN_IF(rv.Failed())) {
return false;
}
return true;
}
JSObject* DocumentL10n::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return DocumentL10n_Binding::Wrap(aCx, this, aGivenProto);

View File

@ -47,12 +47,10 @@ class DocumentL10n final : public DOMLocalization {
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocumentL10n, DOMLocalization)
static RefPtr<DocumentL10n> Create(Document* aDocument, const bool aSync);
static RefPtr<DocumentL10n> Create(Document* aDocument, bool aSync);
protected:
explicit DocumentL10n(Document* aDocument, const bool aSync);
bool Init() override;
explicit DocumentL10n(Document* aDocument, bool aSync);
virtual ~DocumentL10n() = default;
RefPtr<Document> mDocument;

View File

@ -516,7 +516,7 @@ void L10nOverlays::TranslateElement(Element& aElement,
DocumentFragment(aElement.OwnerDoc()->NodeInfoManager());
nsContentUtils::ParseFragmentHTML(
NS_ConvertUTF8toUTF16(aTranslation.mValue), fragment,
nsGkAtoms::_template, kNameSpaceID_XHTML, false, true);
nsGkAtoms::_template, kNameSpaceID_XHTML, false, true, 0);
if (NS_WARN_IF(aRv.Failed())) {
return;
}

View File

@ -114,18 +114,15 @@ void MediaControlService::Init() {
mControllerManager = MakeUnique<ControllerManager>(this);
// Initialize the fallback title
nsCOMPtr<nsIGlobalObject> global =
xpc::NativeGlobal(xpc::PrivilegedJunkScope());
RefPtr<Localization> l10n = Localization::Create(global, true, {});
l10n->AddResourceId(u"branding/brand.ftl"_ns);
l10n->AddResourceId(u"dom/media.ftl"_ns);
nsTArray<nsCString> resIds{
"branding/brand.ftl"_ns,
"dom/media.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
{
AutoSafeJSContext cx;
nsAutoCString translation;
ErrorResult rv;
l10n->FormatValueSync(cx, "mediastatus-fallback-title"_ns, {}, translation,
rv);
IgnoredErrorResult rv;
l10n->FormatValueSync("mediastatus-fallback-title"_ns, {}, translation, rv);
if (!rv.Failed()) {
mFallbackTitle = NS_ConvertUTF8toUTF16(translation);
}

View File

@ -92,16 +92,13 @@ interface Localization {
* default generators provided by Gecko.
*/
[Throws]
constructor(sequence<DOMString> aResourceIds,
optional boolean aSync = false,
optional BundleGenerator aBundleGenerator = {});
constructor(sequence<UTF8String> aResourceIds,
optional boolean aSync = false);
/**
* A method for adding resources to the localization context.
*
* Returns a new count of resources used by the context.
*/
unsigned long addResourceIds(sequence<DOMString> aResourceIds);
void addResourceIds(sequence<DOMString> aResourceIds);
/**
* A method for removing resources from the localization context.

View File

@ -188,8 +188,10 @@ bool extendJSArrayWithErrors(JSContext* aCx, JS::Handle<JSObject*> aErrors,
return true;
}
static void ConvertArgs(const L10nArgs& aArgs,
nsTArray<ffi::L10nArg>& aRetVal) {
/* static */
void FluentBundle::ConvertArgs(const L10nArgs& aArgs,
nsTArray<ffi::L10nArg>& aRetVal) {
aRetVal.SetCapacity(aArgs.Entries().Length());
for (const auto& entry : aArgs.Entries()) {
if (!entry.mValue.IsNull()) {
const auto& value = entry.mValue.Value();

View File

@ -81,6 +81,9 @@ class FluentBundle final : public nsWrapperCache {
const dom::Optional<JS::Handle<JSObject*>>& aErrors,
nsACString& aRetVal, ErrorResult& aRv);
static void ConvertArgs(const L10nArgs& aArgs,
nsTArray<ffi::L10nArg>& aRetVal);
protected:
virtual ~FluentBundle();

View File

@ -5,162 +5,137 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Localization.h"
#include "nsImportModule.h"
#include "nsIObserverService.h"
#include "nsContentUtils.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "js/PropertyAndElement.h" // JS_GetProperty
#define INTL_APP_LOCALES_CHANGED "intl:app-locales-changed"
#define L10N_PSEUDO_PREF "intl.l10n.pseudo"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::intl;
static const char* kObservedPrefs[] = {L10N_PSEUDO_PREF, nullptr};
using namespace mozilla::intl;
using namespace mozilla::dom;
static nsTArray<ffi::L10nKey> ConvertFromL10nKeys(
const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys) {
nsTArray<ffi::L10nKey> l10nKeys(aKeys.Length());
NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(Localization)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Localization)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalization)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
tmp->Destroy();
mozilla::DropJSObjects(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Localization)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalization)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
for (const auto& entry : aKeys) {
if (entry.IsUTF8String()) {
const auto& id = entry.GetAsUTF8String();
ffi::L10nKey* key = l10nKeys.AppendElement();
key->id = &id;
} else {
const auto& e = entry.GetAsL10nIdArgs();
ffi::L10nKey* key = l10nKeys.AppendElement();
key->id = &e.mId;
if (!e.mArgs.IsNull()) {
FluentBundle::ConvertArgs(e.mArgs.Value(), key->args);
}
}
}
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Localization)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGenerateBundles)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGenerateBundlesSync)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mBundles)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
return l10nKeys;
}
static FallibleTArray<AttributeNameValue> ConvertToAttributeNameValue(
const nsTArray<ffi::L10nAttribute>& aAttributes, OOMReporter& aError) {
FallibleTArray<AttributeNameValue> result(aAttributes.Length());
for (const auto& attr : aAttributes) {
auto cvtAttr = AttributeNameValue();
cvtAttr.mName = attr.name;
cvtAttr.mValue = attr.value;
if (!result.AppendElement(std::move(cvtAttr), fallible)) {
result.Clear();
aError.ReportOOM();
return result;
}
}
return result;
}
static FallibleTArray<Nullable<L10nMessage>> ConvertToL10nMessages(
const nsTArray<ffi::OptionalL10nMessage>& aMessages, ErrorResult& aError) {
FallibleTArray<Nullable<L10nMessage>> l10nMessages(aMessages.Length());
for (const auto& entry : aMessages) {
Nullable<L10nMessage> msg;
if (entry.is_present) {
L10nMessage& m = msg.SetValue();
if (!entry.message.value.IsVoid()) {
m.mValue = entry.message.value;
}
if (!entry.message.attributes.IsEmpty()) {
m.mAttributes.SetValue(
ConvertToAttributeNameValue(entry.message.attributes, aError));
}
}
if (!l10nMessages.AppendElement(std::move(msg), fallible)) {
l10nMessages.Clear();
aError.Throw(NS_ERROR_OUT_OF_MEMORY);
return l10nMessages;
}
}
return l10nMessages;
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Localization, mGlobal)
NS_IMPL_CYCLE_COLLECTING_ADDREF(Localization)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Localization)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Localization)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/* static */
already_AddRefed<Localization> Localization::Create(
nsIGlobalObject* aGlobal, const bool aSync,
const BundleGenerator& aBundleGenerator) {
RefPtr<Localization> loc = new Localization(aGlobal, aSync, aBundleGenerator);
loc->Init();
return loc.forget();
const nsTArray<nsCString>& aResourceIds, bool aIsSync) {
return MakeAndAddRef<Localization>(aResourceIds, aIsSync);
}
Localization::Localization(nsIGlobalObject* aGlobal, const bool aSync,
const BundleGenerator& aBundleGenerator)
: mGlobal(aGlobal), mIsSync(aSync) {
if (aBundleGenerator.mGenerateBundles.WasPassed()) {
GenerateBundles& generateBundles =
aBundleGenerator.mGenerateBundles.Value();
mGenerateBundles.setObject(*generateBundles.CallbackOrNull());
}
if (aBundleGenerator.mGenerateBundlesSync.WasPassed()) {
GenerateBundlesSync& generateBundlesSync =
aBundleGenerator.mGenerateBundlesSync.Value();
mGenerateBundlesSync.setObject(*generateBundlesSync.CallbackOrNull());
}
mIsSync = aSync;
Localization::Localization(const nsTArray<nsCString>& aResIds, bool aIsSync)
: mIsSync(aIsSync) {
ffi::localization_new(&aResIds, mIsSync, getter_AddRefs(mRaw));
}
bool Localization::Init() {
RegisterObservers();
return true;
Localization::Localization(nsIGlobalObject* aGlobal,
const nsTArray<nsCString>& aResIds, bool aIsSync)
: mGlobal(aGlobal), mIsSync(aIsSync) {
ffi::localization_new(&aResIds, mIsSync, getter_AddRefs(mRaw));
}
void Localization::Activate(const bool aEager) {
mLocalization = do_ImportModule("resource://gre/modules/Localization.jsm",
"Localization");
Localization::Localization(nsIGlobalObject* aGlobal, bool aIsSync)
: mGlobal(aGlobal), mIsSync(aIsSync) {
nsTArray<nsCString> resIds;
ffi::localization_new(&resIds, mIsSync, getter_AddRefs(mRaw));
}
AutoJSContext cx;
JS::Rooted<JS::Value> generateBundlesJS(cx, mGenerateBundles);
JS::Rooted<JS::Value> generateBundlesSyncJS(cx, mGenerateBundlesSync);
JS::Rooted<JS::Value> bundlesJS(cx);
mLocalization->GenerateBundles(mResourceIds, mIsSync, aEager,
generateBundlesJS, generateBundlesSyncJS,
&bundlesJS);
mBundles.set(bundlesJS);
mozilla::HoldJSObjects(this);
Localization::Localization(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal), mIsSync(false) {
nsTArray<nsCString> resIds;
ffi::localization_new(&resIds, mIsSync, getter_AddRefs(mRaw));
}
already_AddRefed<Localization> Localization::Constructor(
const GlobalObject& aGlobal, const Sequence<nsString>& aResourceIds,
const bool aSync, const BundleGenerator& aBundleGenerator,
ErrorResult& aRv) {
const GlobalObject& aGlobal, const Sequence<nsCString>& aResourceIds,
bool aIsSync, ErrorResult& aRv) {
nsTArray<nsCString> resIds = ToTArray<nsTArray<nsCString>>(aResourceIds);
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
RefPtr<Localization> loc =
Localization::Create(global, aSync, aBundleGenerator);
if (aResourceIds.Length()) {
loc->AddResourceIds(aResourceIds);
}
loc->Activate(true);
return loc.forget();
return do_AddRef(new Localization(global, resIds, aIsSync));
}
nsIGlobalObject* Localization::GetParentObject() const { return mGlobal; }
JSObject* Localization::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return Localization_Binding::Wrap(aCx, this, aGivenProto);
}
Localization::~Localization() {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, INTL_APP_LOCALES_CHANGED);
}
Preferences::RemoveObservers(this, kObservedPrefs);
Destroy();
mozilla::DropJSObjects(this);
}
void Localization::Destroy() {
mGenerateBundles.setUndefined();
mGenerateBundlesSync.setUndefined();
mBundles.setUndefined();
}
/* Protected */
void Localization::RegisterObservers() {
DebugOnly<nsresult> rv = Preferences::AddWeakObservers(this, kObservedPrefs);
MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->AddObserver(this, INTL_APP_LOCALES_CHANGED, true);
}
}
Localization::~Localization() = default;
NS_IMETHODIMP
Localization::Observe(nsISupports* aSubject, const char* aTopic,
@ -178,261 +153,218 @@ Localization::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
void Localization::OnChange() {
if (mLocalization) {
AutoJSContext cx;
JS::Rooted<JS::Value> generateBundlesJS(cx, mGenerateBundles);
JS::Rooted<JS::Value> generateBundlesSyncJS(cx, mGenerateBundlesSync);
JS::Rooted<JS::Value> bundlesJS(cx);
mLocalization->GenerateBundles(mResourceIds, mIsSync, false,
generateBundlesJS, generateBundlesSyncJS,
&bundlesJS);
mBundles.set(bundlesJS);
}
void Localization::OnChange() {}
void Localization::SetIsSync(bool aIsSync) {
MOZ_ASSERT(!aIsSync, "We should only move from sync to async!");
mIsSync = aIsSync;
Upgrade();
}
uint32_t Localization::AddResourceId(const nsAString& aResourceId) {
if (!mResourceIds.Contains(aResourceId)) {
mResourceIds.AppendElement(aResourceId);
Localization::OnChange();
}
return mResourceIds.Length();
void Localization::AddResourceId(const nsAString& aResourceId) {
NS_ConvertUTF16toUTF8 resId(aResourceId);
ffi::localization_add_res_id(mRaw.get(), &resId);
}
uint32_t Localization::RemoveResourceId(const nsAString& aResourceId) {
if (mResourceIds.RemoveElement(aResourceId)) {
Localization::OnChange();
}
return mResourceIds.Length();
NS_ConvertUTF16toUTF8 resId(aResourceId);
return ffi::localization_remove_res_id(mRaw.get(), &resId);
}
/**
* Localization API
*/
uint32_t Localization::AddResourceIds(const nsTArray<nsString>& aResourceIds) {
bool added = false;
void Localization::AddResourceIds(const nsTArray<nsString>& aResourceIds) {
nsTArray<nsCString> resIds(aResourceIds.Length());
for (const auto& resId : aResourceIds) {
if (!mResourceIds.Contains(resId)) {
mResourceIds.AppendElement(resId);
added = true;
}
resIds.AppendElement(NS_ConvertUTF16toUTF8(resId));
}
if (added) {
Localization::OnChange();
}
return mResourceIds.Length();
ffi::localization_add_res_ids(mRaw.get(), &resIds);
}
uint32_t Localization::RemoveResourceIds(
const nsTArray<nsString>& aResourceIds) {
bool removed = false;
nsTArray<nsCString> resIds(aResourceIds.Length());
for (const auto& resId : aResourceIds) {
if (mResourceIds.RemoveElement(resId)) {
removed = true;
}
resIds.AppendElement(NS_ConvertUTF16toUTF8(resId));
}
if (removed) {
Localization::OnChange();
}
return mResourceIds.Length();
return ffi::localization_remove_res_ids(mRaw.get(), &resIds);
}
already_AddRefed<Promise> Localization::FormatValue(
JSContext* aCx, const nsACString& aId, const Optional<L10nArgs>& aArgs,
ErrorResult& aRv) {
if (!mLocalization) {
Activate(false);
}
JS::Rooted<JS::Value> args(aCx);
const nsACString& aId, const Optional<L10nArgs>& aArgs, ErrorResult& aRv) {
nsTArray<ffi::L10nArg> l10nArgs;
nsTArray<nsCString> errors;
if (aArgs.WasPassed()) {
ConvertL10nArgsToJSValue(aCx, aArgs.Value(), &args, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
} else {
args = JS::UndefinedValue();
const L10nArgs& args = aArgs.Value();
FluentBundle::ConvertArgs(args, l10nArgs);
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
ffi::localization_format_value(
mRaw.get(), &aId, &l10nArgs, promise,
[](const Promise* aPromise, const nsACString* aValue,
const nsTArray<nsCString>* aErrors) {
Promise* promise = const_cast<Promise*>(aPromise);
if (!aErrors->IsEmpty()) {
ErrorResult rv;
rv.ThrowInvalidStateError(aErrors->ElementAt(0));
promise->MaybeReject(std::move(rv));
} else {
promise->MaybeResolve(aValue);
}
});
RefPtr<Promise> promise;
JS::Rooted<JS::Value> bundlesJS(aCx, mBundles);
nsresult rv = mLocalization->FormatValue(mResourceIds, bundlesJS, aId, args,
getter_AddRefs(promise));
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return nullptr;
}
return MaybeWrapPromise(promise);
}
void Localization::SetIsSync(const bool aIsSync) { mIsSync = aIsSync; }
already_AddRefed<Promise> Localization::FormatValues(
JSContext* aCx, const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
ErrorResult& aRv) {
if (!mLocalization) {
Activate(false);
}
nsTArray<JS::Value> jsKeys;
SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
for (auto& key : aKeys) {
JS::RootedValue jsKey(aCx);
if (!ToJSValue(aCx, key, &jsKey)) {
aRv.NoteJSContextException(aCx);
return nullptr;
}
jsKeys.AppendElement(jsKey);
}
const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys, ErrorResult& aRv) {
nsTArray<ffi::L10nKey> l10nKeys = ConvertFromL10nKeys(aKeys);
RefPtr<Promise> promise;
JS::Rooted<JS::Value> bundlesJS(aCx, mBundles);
aRv = mLocalization->FormatValues(mResourceIds, bundlesJS, jsKeys,
getter_AddRefs(promise));
if (NS_WARN_IF(aRv.Failed())) {
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (aRv.Failed()) {
return nullptr;
}
ffi::localization_format_values(
mRaw.get(), &l10nKeys, promise,
// callback function which will be invoked by the rust code, passing the
// promise back in.
[](const Promise* aPromise, const nsTArray<nsCString>* aRaw) {
Promise* promise = const_cast<Promise*>(aPromise);
promise->MaybeResolve(*aRaw);
});
return MaybeWrapPromise(promise);
}
already_AddRefed<Promise> Localization::FormatMessages(
JSContext* aCx, const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
ErrorResult& aRv) {
if (!mLocalization) {
Activate(false);
}
nsTArray<JS::Value> jsKeys;
SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
for (auto& key : aKeys) {
JS::RootedValue jsKey(aCx);
if (!ToJSValue(aCx, key, &jsKey)) {
aRv.NoteJSContextException(aCx);
return nullptr;
}
jsKeys.AppendElement(jsKey);
}
const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys, ErrorResult& aRv) {
auto l10nKeys = ConvertFromL10nKeys(aKeys);
RefPtr<Promise> promise;
JS::Rooted<JS::Value> bundlesJS(aCx, mBundles);
aRv = mLocalization->FormatMessages(mResourceIds, bundlesJS, jsKeys,
getter_AddRefs(promise));
if (NS_WARN_IF(aRv.Failed())) {
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (aRv.Failed()) {
return nullptr;
}
ffi::localization_format_messages(
mRaw.get(), &l10nKeys, promise,
// callback function which will be invoked by the rust code, passing the
// promise back in.
[](const Promise* aPromise,
const nsTArray<ffi::OptionalL10nMessage>* aRaw,
const nsTArray<nsCString>* aErrors) {
Promise* promise = const_cast<Promise*>(aPromise);
if (!aErrors->IsEmpty()) {
// XXX: We should consider throwing an array of errors here.
promise->MaybeRejectWithInvalidStateError(aErrors->ElementAt(0));
} else {
ErrorResult rv;
FallibleTArray<Nullable<L10nMessage>> messages;
if (aRaw) {
messages = ConvertToL10nMessages(*aRaw, rv);
}
if (rv.Failed()) {
promise->MaybeReject(std::move(rv));
} else {
promise->MaybeResolve(messages);
}
}
});
return MaybeWrapPromise(promise);
}
void Localization::FormatValueSync(JSContext* aCx, const nsACString& aId,
void Localization::FormatValueSync(const nsACString& aId,
const Optional<L10nArgs>& aArgs,
nsACString& aRetVal, ErrorResult& aRv) {
if (!mIsSync) {
aRv.ThrowInvalidStateError(
"Can't use formatValueSync when state is async.");
return;
}
if (!mLocalization) {
Activate(false);
}
JS::Rooted<JS::Value> args(aCx);
nsTArray<ffi::L10nArg> l10nArgs;
nsTArray<nsCString> errors;
if (aArgs.WasPassed()) {
ConvertL10nArgsToJSValue(aCx, aArgs.Value(), &args, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
args = JS::UndefinedValue();
const L10nArgs& args = aArgs.Value();
FluentBundle::ConvertArgs(args, l10nArgs);
}
JS::Rooted<JS::Value> bundlesJS(aCx, mBundles);
aRv = mLocalization->FormatValueSync(mResourceIds, bundlesJS, aId, args,
aRetVal);
ffi::localization_format_value_sync(mRaw.get(), &aId, &l10nArgs, &aRetVal,
&errors);
if (!errors.IsEmpty()) {
aRv.ThrowInvalidStateError(errors.ElementAt(0));
}
}
void Localization::FormatValuesSync(
JSContext* aCx, const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
nsTArray<nsCString>& aRetVal, ErrorResult& aRv) {
if (!mIsSync) {
aRv.ThrowInvalidStateError(
"Can't use formatValuesSync when state is async.");
return;
}
if (!mLocalization) {
Activate(false);
}
nsTArray<JS::Value> jsKeys;
SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
for (auto& key : aKeys) {
JS::RootedValue jsKey(aCx);
if (!ToJSValue(aCx, key, &jsKey)) {
aRv.NoteJSContextException(aCx);
return;
}
jsKeys.AppendElement(jsKey);
}
nsTArray<ffi::L10nKey> l10nKeys(aKeys.Length());
nsTArray<nsCString> errors;
JS::Rooted<JS::Value> bundlesJS(aCx, mBundles);
aRv =
mLocalization->FormatValuesSync(mResourceIds, bundlesJS, jsKeys, aRetVal);
}
void Localization::FormatMessagesSync(
JSContext* aCx, const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
nsTArray<Nullable<L10nMessage>>& aRetVal, ErrorResult& aRv) {
if (!mIsSync) {
aRv.ThrowInvalidStateError(
"Can't use formatMessagesSync when state is async.");
return;
}
if (!mLocalization) {
Activate(false);
}
nsTArray<JS::Value> jsKeys;
SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
for (auto& key : aKeys) {
JS::RootedValue jsKey(aCx);
if (!ToJSValue(aCx, key, &jsKey)) {
aRv.NoteJSContextException(aCx);
return;
}
jsKeys.AppendElement(jsKey);
}
nsTArray<JS::Value> messages;
SequenceRooter<JS::Value> messagesRooter(aCx, &messages);
JS::Rooted<JS::Value> bundlesJS(aCx, mBundles);
aRv = mLocalization->FormatMessagesSync(mResourceIds, bundlesJS, jsKeys,
messages);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
JS::Rooted<JS::Value> rootedMsg(aCx);
for (auto& msg : messages) {
rootedMsg.set(msg);
Nullable<L10nMessage>* slotPtr = aRetVal.AppendElement(mozilla::fallible);
if (!slotPtr) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
if (rootedMsg.isNull()) {
slotPtr->SetNull();
for (const auto& entry : aKeys) {
if (entry.IsUTF8String()) {
const auto& id = entry.GetAsUTF8String();
nsTArray<ffi::L10nArg> l10nArgs;
ffi::L10nKey* key = l10nKeys.AppendElement();
key->id = &id;
} else {
JS_WrapValue(aCx, &rootedMsg);
if (!slotPtr->SetValue().Init(aCx, rootedMsg)) {
aRv.NoteJSContextException(aCx);
return;
const auto& e = entry.GetAsL10nIdArgs();
nsTArray<ffi::L10nArg> l10nArgs;
ffi::L10nKey* key = l10nKeys.AppendElement();
key->id = &e.mId;
if (!e.mArgs.IsNull()) {
FluentBundle::ConvertArgs(e.mArgs.Value(), key->args);
}
}
}
ffi::localization_format_values_sync(mRaw.get(), &l10nKeys, &aRetVal,
&errors);
if (!errors.IsEmpty()) {
aRv.ThrowInvalidStateError(errors.ElementAt(0));
}
}
void Localization::FormatMessagesSync(
const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
nsTArray<Nullable<L10nMessage>>& aRetVal, ErrorResult& aRv) {
nsTArray<ffi::L10nKey> l10nKeys(aKeys.Length());
nsTArray<nsCString> errors;
for (const auto& entry : aKeys) {
if (entry.IsUTF8String()) {
const auto& id = entry.GetAsUTF8String();
nsTArray<ffi::L10nArg> l10nArgs;
ffi::L10nKey* key = l10nKeys.AppendElement();
key->id = &id;
} else {
const auto& e = entry.GetAsL10nIdArgs();
nsTArray<ffi::L10nArg> l10nArgs;
ffi::L10nKey* key = l10nKeys.AppendElement();
key->id = &e.mId;
if (!e.mArgs.IsNull()) {
FluentBundle::ConvertArgs(e.mArgs.Value(), key->args);
}
}
}
nsTArray<ffi::OptionalL10nMessage> result(l10nKeys.Length());
ffi::localization_format_messages_sync(mRaw.get(), &l10nKeys, &result,
&errors);
if (!errors.IsEmpty()) {
aRv.ThrowInvalidStateError(errors.ElementAt(0));
return;
}
aRetVal = ConvertToL10nMessages(result, aRv);
}
void Localization::Upgrade() { ffi::localization_upgrade(mRaw.get()); }
/**
* PromiseResolver is a PromiseNativeHandler used
* by MaybeWrapPromise method.
@ -501,33 +433,3 @@ already_AddRefed<Promise> Localization::MaybeWrapPromise(
aInnerPromise->AppendNativeHandler(resolver);
return docPromise.forget();
}
void Localization::ConvertL10nArgsToJSValue(
JSContext* aCx, const L10nArgs& aArgs, JS::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv) {
// This method uses a temporary dictionary to automate
// converting an IDL Record to a JS Value via a dictionary.
//
// Once we get ToJSValue for Record, we'll switch to that.
L10nArgsHelperDict helperDict;
for (auto& entry : aArgs.Entries()) {
L10nArgs::EntryType* newEntry =
helperDict.mArgs.Entries().AppendElement(fallible);
if (!newEntry) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
newEntry->mKey = entry.mKey;
newEntry->mValue = entry.mValue;
}
JS::Rooted<JS::Value> jsVal(aCx);
if (!ToJSValue(aCx, helperDict, &jsVal)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
JS::Rooted<JSObject*> jsObj(aCx, &jsVal.toObject());
if (!JS_GetProperty(aCx, jsObj, "args", aRetVal)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
}

View File

@ -7,109 +7,83 @@
#ifndef mozilla_intl_l10n_Localization_h
#define mozilla_intl_l10n_Localization_h
#include "nsWeakReference.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIObserver.h"
#include "mozILocalization.h"
#include "nsWrapperCache.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/LocalizationBinding.h"
#include "mozilla/dom/PromiseNativeHandler.h"
class nsIGlobalObject;
using namespace mozilla::dom;
#include "mozilla/intl/LocalizationBindings.h"
namespace mozilla {
class ErrorResult;
namespace intl {
typedef Record<nsCString, Nullable<OwningUTF8StringOrDouble>> L10nArgs;
class Localization : public nsIObserver, public nsWrapperCache {
template <typename T, typename... Args>
friend already_AddRefed<T> mozilla::MakeAndAddRef(Args&&... aArgs);
class Localization : public nsIObserver,
public nsSupportsWeakReference,
public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Localization,
nsIObserver)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Localization)
NS_DECL_NSIOBSERVER
static already_AddRefed<Localization> Create(
nsIGlobalObject* aGlobal, const bool aSync,
const BundleGenerator& aBundleGenerator);
void Activate(const bool aEager);
void Destroy();
static already_AddRefed<Localization> Constructor(
const GlobalObject& aGlobal, const Sequence<nsString>& aResourceIds,
const bool aSync, const BundleGenerator& aBundleGenerator,
const dom::GlobalObject& aGlobal,
const dom::Sequence<nsCString>& aResourceIds, bool aIsSync,
ErrorResult& aRv);
static already_AddRefed<Localization> Create(
const nsTArray<nsCString>& aResourceIds, bool aIsSync);
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
nsIGlobalObject* GetParentObject() const { return mGlobal; }
void SetIsSync(bool aIsSync);
already_AddRefed<dom::Promise> FormatValue(
const nsACString& aId, const dom::Optional<L10nArgs>& aArgs,
ErrorResult& aRv);
nsIGlobalObject* GetParentObject() const;
already_AddRefed<dom::Promise> FormatValues(
const dom::Sequence<dom::OwningUTF8StringOrL10nIdArgs>& aKeys,
ErrorResult& aRv);
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
already_AddRefed<dom::Promise> FormatMessages(
const dom::Sequence<dom::OwningUTF8StringOrL10nIdArgs>& aKeys,
ErrorResult& aRv);
uint32_t AddResourceId(const nsAString& aResourceId);
void FormatValueSync(const nsACString& aId,
const dom::Optional<L10nArgs>& aArgs,
nsACString& aRetVal, ErrorResult& aRv);
void FormatValuesSync(
const dom::Sequence<dom::OwningUTF8StringOrL10nIdArgs>& aKeys,
nsTArray<nsCString>& aRetVal, ErrorResult& aRv);
void FormatMessagesSync(
const dom::Sequence<dom::OwningUTF8StringOrL10nIdArgs>& aKeys,
nsTArray<dom::Nullable<dom::L10nMessage>>& aRetVal, ErrorResult& aRv);
void AddResourceId(const nsAString& aResourceId);
uint32_t RemoveResourceId(const nsAString& aResourceId);
/**
* Localization API
*
* Methods documentation in Localization.webidl
*/
uint32_t AddResourceIds(const nsTArray<nsString>& aResourceIds);
void AddResourceIds(const nsTArray<nsString>& aResourceIds);
uint32_t RemoveResourceIds(const nsTArray<nsString>& aResourceIds);
already_AddRefed<Promise> FormatValue(JSContext* aCx, const nsACString& aId,
const Optional<L10nArgs>& aArgs,
ErrorResult& aRv);
already_AddRefed<Promise> FormatValues(
JSContext* aCx, const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
ErrorResult& aRv);
already_AddRefed<Promise> FormatMessages(
JSContext* aCx, const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
ErrorResult& aRv);
void SetIsSync(const bool aIsSync);
void FormatValueSync(JSContext* aCx, const nsACString& aId,
const Optional<L10nArgs>& aArgs, nsACString& aRetVal,
ErrorResult& aRv);
void FormatValuesSync(JSContext* aCx,
const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
nsTArray<nsCString>& aRetVal, ErrorResult& aRv);
void FormatMessagesSync(JSContext* aCx,
const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
nsTArray<Nullable<L10nMessage>>& aRetVal,
ErrorResult& aRv);
void Upgrade();
protected:
Localization(nsIGlobalObject* aGlobal, const bool aSync,
const BundleGenerator& aBundleGenerator);
virtual bool Init();
Localization(const nsTArray<nsCString>& aResIds, bool aIsSync);
Localization(nsIGlobalObject* aGlobal, const nsTArray<nsCString>& aResIds,
bool aIsSync);
explicit Localization(nsIGlobalObject* aGlobal);
Localization(nsIGlobalObject* aGlobal, bool aIsSync);
virtual ~Localization();
void RegisterObservers();
virtual void OnChange();
already_AddRefed<Promise> MaybeWrapPromise(Promise* aInnerPromise);
void ConvertL10nArgsToJSValue(JSContext* aCx, const L10nArgs& aArgs,
JS::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv);
already_AddRefed<dom::Promise> MaybeWrapPromise(dom::Promise* aInnerPromise);
nsCOMPtr<nsIGlobalObject> mGlobal;
nsCOMPtr<mozILocalization> mLocalization;
RefPtr<const ffi::LocalizationRc> mRaw;
bool mIsSync;
nsTArray<nsString> mResourceIds;
JS::Heap<JS::Value> mBundles;
JS::Heap<JS::Value> mGenerateBundles;
JS::Heap<JS::Value> mGenerateBundlesSync;
};
} // namespace intl

View File

@ -301,7 +301,7 @@ impl LocalizationRc {
&self,
keys: &ThinVec<L10nKey>,
promise: &xpcom::Promise,
callback: extern "C" fn(&xpcom::Promise, Option<&ThinVec<nsCString>>),
callback: extern "C" fn(&xpcom::Promise, &ThinVec<nsCString>),
) {
let bundles = self.inner.borrow().bundles().clone();
@ -326,7 +326,7 @@ impl LocalizationRc {
})
.collect::<ThinVec<_>>();
callback(&strong_promise, Some(&ret_val));
callback(&strong_promise, &ret_val);
})
.expect("Failed to spawn future");
}
@ -337,7 +337,7 @@ impl LocalizationRc {
promise: &xpcom::Promise,
callback: extern "C" fn(
&xpcom::Promise,
Option<&ThinVec<OptionalL10nMessage>>,
&ThinVec<OptionalL10nMessage>,
&ThinVec<nsCString>,
),
) {
@ -375,7 +375,7 @@ impl LocalizationRc {
.map(|err| err.to_string().into())
.collect();
callback(&strong_promise, Some(&ret_val), &errors);
callback(&strong_promise, &ret_val, &errors);
})
.expect("Failed to spawn future");
}
@ -483,7 +483,7 @@ pub extern "C" fn localization_format_values(
loc: &LocalizationRc,
keys: &ThinVec<L10nKey>,
promise: &xpcom::Promise,
callback: extern "C" fn(&xpcom::Promise, Option<&ThinVec<nsCString>>),
callback: extern "C" fn(&xpcom::Promise, &ThinVec<nsCString>),
) {
loc.format_values(keys, promise, callback);
}
@ -493,11 +493,7 @@ pub extern "C" fn localization_format_messages(
loc: &LocalizationRc,
keys: &ThinVec<L10nKey>,
promise: &xpcom::Promise,
callback: extern "C" fn(
&xpcom::Promise,
Option<&ThinVec<OptionalL10nMessage>>,
&ThinVec<nsCString>,
),
callback: extern "C" fn(&xpcom::Promise, &ThinVec<OptionalL10nMessage>, &ThinVec<nsCString>),
) {
loc.format_messages(keys, promise, callback);
}

View File

@ -133,9 +133,10 @@ void nsPrinterListBase::EnsureCommonPaperInfo(JSContext* aCx) {
// available (otherwise leave them as the internal keys, which are at least
// somewhat recognizable).
IgnoredErrorResult rv;
nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
RefPtr<Localization> l10n = Localization::Create(global, true, {});
l10n->AddResourceId(u"toolkit/printing/printUI.ftl"_ns);
nsTArray<nsCString> resIds = {
"toolkit/printing/printUI.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
for (auto i : IntegerRange(nsPaper::kNumCommonPaperSizes)) {
const CommonPaperSize& size = nsPaper::kCommonPaperSizes[i];
@ -144,7 +145,7 @@ void nsPrinterListBase::EnsureCommonPaperInfo(JSContext* aCx) {
nsAutoCString key{"printui-paper-"};
key.Append(size.mLocalizableNameKey);
nsAutoCString name;
l10n->FormatValueSync(aCx, key, {}, name, rv);
l10n->FormatValueSync(key, {}, name, rv);
// Fill out the info with our PWG size and the localized name.
info.mId = size.mPWGName;