Bug 1660392 - [l10nregistry] part2: Add L10nRegistry WebIDL backed by l10nregistry-rs. r=nika,emilio

Depends on D105415

Differential Revision: https://phabricator.services.mozilla.com/D102096
This commit is contained in:
Zibi Braniecki 2021-07-30 16:47:47 +00:00
parent 36a07a5f2a
commit f42090a25a
16 changed files with 979 additions and 100 deletions

5
Cargo.lock generated
View File

@ -1472,6 +1472,7 @@ dependencies = [
name = "fluent-ffi"
version = "0.1.0"
dependencies = [
"cstr",
"fluent",
"fluent-fallback",
"fluent-pseudo",
@ -1481,6 +1482,7 @@ dependencies = [
"nsstring",
"thin-vec",
"unic-langid",
"xpcom",
]
[[package]]
@ -2658,6 +2660,9 @@ dependencies = [
"async-trait",
"cstr",
"fluent",
"fluent-fallback",
"fluent-ffi",
"futures 0.3.15",
"futures-channel",
"l10nregistry",
"libc",

View File

@ -280,6 +280,16 @@ DOMInterfaces = {
'nativeType': 'mozilla::intl::FluentBundle',
},
'FluentBundleAsyncIterator': {
'headerFile': 'mozilla/intl/L10nRegistry.h',
'nativeType': 'mozilla::intl::FluentBundleAsyncIterator',
},
'FluentBundleIterator': {
'headerFile': 'mozilla/intl/L10nRegistry.h',
'nativeType': 'mozilla::intl::FluentBundleIterator',
},
'FluentPattern': {
'headerFile': 'mozilla/intl/FluentBundle.h',
'nativeType': 'mozilla::intl::FluentPattern',
@ -445,6 +455,10 @@ DOMInterfaces = {
'nativeType': 'mozilla::intl::L10nFileSource',
},
'L10nRegistry': {
'nativeType': 'mozilla::intl::L10nRegistry',
},
'LegacyMozTCPSocket': {
'headerFile': 'TCPSocket.h',
'wrapperCache': False,

View File

@ -87,3 +87,46 @@ interface L10nFileSource {
FluentResource? fetchFileSync(UTF8String locale, UTF8String path);
};
dictionary FluentBundleIteratorResult
{
required FluentBundle? value;
required boolean done;
};
[LegacyNoInterfaceObject, Exposed=Window]
interface FluentBundleIterator {
FluentBundleIteratorResult next();
[Alias="@@iterator"] FluentBundleIterator values();
};
[LegacyNoInterfaceObject, Exposed=Window]
interface FluentBundleAsyncIterator {
Promise<FluentBundleIteratorResult> next();
[Alias="@@asyncIterator"] FluentBundleAsyncIterator values();
};
[ChromeOnly, Exposed=Window]
interface L10nRegistry {
constructor();
static L10nRegistry getInstance();
sequence<UTF8String> getAvailableLocales();
void registerSources(sequence<L10nFileSource> aSources);
void updateSources(sequence<L10nFileSource> aSources);
void removeSources(sequence<UTF8String> aSources);
[Throws]
boolean hasSource(UTF8String aName);
[Throws]
L10nFileSource? getSource(UTF8String aName);
sequence<UTF8String> getSourceNames();
void clearSources();
[Throws, NewObject]
FluentBundleIterator generateBundlesSync(sequence<UTF8String> aLocales, sequence<UTF8String> aResourceIds);
[Throws, NewObject]
FluentBundleAsyncIterator generateBundles(sequence<UTF8String> aLocales, sequence<UTF8String> aResourceIds);
};

View File

@ -59,6 +59,8 @@ class L10nFileSource : public nsWrapperCache {
const nsACString& aPath,
ErrorResult& aRv);
const ffi::FileSource* Raw() const { return mRaw; }
protected:
virtual ~L10nFileSource() = default;
nsCOMPtr<nsIGlobalObject> mGlobal;

View File

@ -60,6 +60,8 @@ class FluentBundle final : public nsWrapperCache {
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(FluentBundle)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(FluentBundle)
FluentBundle(nsISupports* aParent, UniquePtr<ffi::FluentBundleRc> aRaw);
static already_AddRefed<FluentBundle> Constructor(
const dom::GlobalObject& aGlobal,
const dom::UTF8StringOrUTF8StringSequence& aLocales,
@ -80,8 +82,6 @@ class FluentBundle final : public nsWrapperCache {
nsACString& aRetVal, ErrorResult& aRv);
protected:
explicit FluentBundle(nsISupports* aParent,
UniquePtr<ffi::FluentBundleRc> aRaw);
virtual ~FluentBundle();
nsCOMPtr<nsISupports> mParent;

View File

@ -2,79 +2,307 @@
* 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 "L10nRegistry.h"
#include "mozilla/RefPtr.h"
#include "mozilla/URLPreloader.h"
#include "nsIChannel.h"
#include "nsILoadInfo.h"
#include "nsIStreamLoader.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsContentUtils.h"
#include "FluentResource.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Preferences.h"
namespace mozilla {
namespace intl {
using namespace mozilla::dom;
class L10nRegistry {
public:
static nsresult Load(const nsACString& aPath,
nsIStreamLoaderObserver* aObserver) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG);
namespace mozilla::intl {
RefPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(
getter_AddRefs(loader), uri, aObserver,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_OTHER);
/* FluentBundleIterator */
return rv;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundleIterator, mGlobal)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentBundleIterator, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentBundleIterator, Release)
FluentBundleIterator::FluentBundleIterator(
nsIGlobalObject* aGlobal, UniquePtr<ffi::GeckoFluentBundleIterator> aRaw)
: mGlobal(aGlobal), mRaw(std::move(aRaw)) {}
JSObject* FluentBundleIterator::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return FluentBundleIterator_Binding::Wrap(aCx, this, aGivenProto);
}
void FluentBundleIterator::Next(FluentBundleIteratorResult& aResult) {
UniquePtr<ffi::FluentBundleRc> raw(
ffi::fluent_bundle_iterator_next(mRaw.get()));
if (!raw) {
aResult.mDone = true;
return;
}
aResult.mDone = false;
aResult.mValue = new FluentBundle(mGlobal, std::move(raw));
}
already_AddRefed<FluentBundleIterator> FluentBundleIterator::Values() {
return do_AddRef(this);
}
/* FluentBundleAsyncIterator */
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundleAsyncIterator, mGlobal)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentBundleAsyncIterator, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentBundleAsyncIterator, Release)
FluentBundleAsyncIterator::FluentBundleAsyncIterator(
nsIGlobalObject* aGlobal,
UniquePtr<ffi::GeckoFluentBundleAsyncIteratorWrapper> aRaw)
: mGlobal(aGlobal), mRaw(std::move(aRaw)) {}
JSObject* FluentBundleAsyncIterator::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return FluentBundleAsyncIterator_Binding::Wrap(aCx, this, aGivenProto);
}
already_AddRefed<Promise> FluentBundleAsyncIterator::Next() {
ErrorResult rv;
RefPtr<Promise> promise = Promise::Create(mGlobal, rv);
if (rv.Failed()) {
return nullptr;
}
static nsresult LoadSync(const nsACString& aPath, void** aData,
uint64_t* aSize) {
nsCOMPtr<nsIURI> uri;
ffi::fluent_bundle_async_iterator_next(
mRaw.get(), promise,
// callback function which will be invoked by the rust code, passing the
// promise back in.
[](auto* aPromise, ffi::FluentBundleRc* aBundle) {
Promise* promise = const_cast<Promise*>(aPromise);
nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath);
NS_ENSURE_SUCCESS(rv, rv);
FluentBundleIteratorResult res;
NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG);
if (aBundle) {
// The Rust caller will transfer the ownership to us.
UniquePtr<ffi::FluentBundleRc> b(aBundle);
nsIGlobalObject* global = promise->GetGlobalObject();
res.mValue = new FluentBundle(global, std::move(b));
res.mDone = false;
} else {
res.mDone = true;
}
promise->MaybeResolve(res);
});
auto result = URLPreloader::ReadURI(uri);
if (result.isOk()) {
auto uri = result.unwrap();
*aData = ToNewCString(uri);
*aSize = uri.Length();
return NS_OK;
}
return promise.forget();
}
auto err = result.unwrapErr();
if (err != NS_ERROR_INVALID_ARG && err != NS_ERROR_NOT_INITIALIZED) {
return err;
}
already_AddRefed<FluentBundleAsyncIterator>
FluentBundleAsyncIterator::Values() {
return do_AddRef(this);
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(
getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
nullptr, /* aPerformanceStorage */
nullptr, /* aLoadGroup */
nullptr, /* aCallbacks */
nsIRequest::LOAD_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
/* L10nRegistry */
nsCOMPtr<nsIInputStream> input;
rv = channel->Open(getter_AddRefs(input));
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(L10nRegistry, mGlobal)
return NS_ReadInputStreamToBuffer(input, aData, -1, aSize);
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(L10nRegistry, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(L10nRegistry, Release)
L10nRegistry::L10nRegistry(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal), mRaw(dont_AddRef(ffi::l10nregistry_new())) {}
L10nRegistry::L10nRegistry(nsIGlobalObject* aGlobal,
RefPtr<const ffi::GeckoL10nRegistry> aRaw)
: mGlobal(aGlobal), mRaw(std::move(aRaw)) {}
/* static */
already_AddRefed<L10nRegistry> L10nRegistry::Constructor(
const GlobalObject& aGlobal) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
return MakeAndAddRef<L10nRegistry>(global);
}
/* static */
already_AddRefed<L10nRegistry> L10nRegistry::GetInstance(
const GlobalObject& aGlobal) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
return MakeAndAddRef<L10nRegistry>(
global, dont_AddRef(ffi::l10nregistry_instance_get()));
}
JSObject* L10nRegistry::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return L10nRegistry_Binding::Wrap(aCx, this, aGivenProto);
}
void L10nRegistry::GetAvailableLocales(nsTArray<nsCString>& aRetVal) {
ffi::l10nregistry_get_available_locales(mRaw.get(), &aRetVal);
}
void L10nRegistry::RegisterSources(
const Sequence<OwningNonNull<L10nFileSource>>& aSources) {
nsTArray<const ffi::FileSource*> sources(aSources.Length());
for (const auto& source : aSources) {
sources.AppendElement(source->Raw());
}
};
} // namespace intl
} // namespace mozilla
ffi::l10nregistry_register_sources(mRaw.get(), &sources);
}
void L10nRegistry::UpdateSources(
const Sequence<OwningNonNull<L10nFileSource>>& aSources) {
nsTArray<const ffi::FileSource*> sources(aSources.Length());
for (const auto& source : aSources) {
sources.AppendElement(source->Raw());
}
ffi::l10nregistry_update_sources(mRaw.get(), &sources);
}
void L10nRegistry::RemoveSources(const Sequence<nsCString>& aSources) {
ffi::l10nregistry_remove_sources(mRaw.get(), aSources.Elements(),
aSources.Length());
}
bool L10nRegistry::HasSource(const nsACString& aName, ErrorResult& aRv) {
ffi::L10nRegistryStatus status;
bool result = ffi::l10nregistry_has_source(mRaw.get(), &aName, &status);
PopulateError(aRv, status);
return result;
}
already_AddRefed<L10nFileSource> L10nRegistry::GetSource(
const nsACString& aName, ErrorResult& aRv) {
ffi::L10nRegistryStatus status;
RefPtr<const ffi::FileSource> raw(
dont_AddRef(ffi::l10nregistry_get_source(mRaw.get(), &aName, &status)));
if (PopulateError(aRv, status)) {
return nullptr;
}
return MakeAndAddRef<L10nFileSource>(std::move(raw));
}
void L10nRegistry::GetSourceNames(nsTArray<nsCString>& aRetVal) {
ffi::l10nregistry_get_source_names(mRaw.get(), &aRetVal);
}
void L10nRegistry::ClearSources() {
ffi::l10nregistry_clear_sources(mRaw.get());
}
already_AddRefed<FluentBundleIterator> L10nRegistry::GenerateBundlesSync(
const Sequence<nsCString>& aLocales,
const Sequence<nsCString>& aResourceIds, ErrorResult& aRv) {
ffi::L10nRegistryStatus status;
UniquePtr<ffi::GeckoFluentBundleIterator> iter(
ffi::l10nregistry_generate_bundles_sync(
mRaw, aLocales.Elements(), aLocales.Length(), aResourceIds.Elements(),
aResourceIds.Length(), &status));
if (PopulateError(aRv, status) || !iter) {
return nullptr;
}
return do_AddRef(new FluentBundleIterator(mGlobal, std::move(iter)));
}
already_AddRefed<FluentBundleAsyncIterator> L10nRegistry::GenerateBundles(
const Sequence<nsCString>& aLocales,
const Sequence<nsCString>& aResourceIds, ErrorResult& aRv) {
ffi::L10nRegistryStatus status;
UniquePtr<ffi::GeckoFluentBundleAsyncIteratorWrapper> iter(
ffi::l10nregistry_generate_bundles(
mRaw, aLocales.Elements(), aLocales.Length(), aResourceIds.Elements(),
aResourceIds.Length(), &status));
if (PopulateError(aRv, status) || !iter) {
return nullptr;
}
return do_AddRef(new FluentBundleAsyncIterator(mGlobal, std::move(iter)));
}
/* static */
nsresult L10nRegistry::Load(const nsACString& aPath,
nsIStreamLoaderObserver* aObserver) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG);
RefPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(
getter_AddRefs(loader), uri, aObserver,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_OTHER);
return rv;
}
/* static */
nsresult L10nRegistry::LoadSync(const nsACString& aPath, void** aData,
uint64_t* aSize) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG);
auto result = URLPreloader::ReadURI(uri);
if (result.isOk()) {
auto uri = result.unwrap();
*aData = ToNewCString(uri);
*aSize = uri.Length();
return NS_OK;
}
auto err = result.unwrapErr();
if (err != NS_ERROR_INVALID_ARG && err != NS_ERROR_NOT_INITIALIZED) {
return err;
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(
getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
nullptr, /* aPerformanceStorage */
nullptr, /* aLoadGroup */
nullptr, /* aCallbacks */
nsIRequest::LOAD_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> input;
rv = channel->Open(getter_AddRefs(input));
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
return NS_ReadInputStreamToBuffer(input, aData, -1, aSize);
}
/* static */
bool L10nRegistry::PopulateError(ErrorResult& aError,
ffi::L10nRegistryStatus& aStatus) {
switch (aStatus) {
case ffi::L10nRegistryStatus::InvalidLocaleCode:
aError.ThrowTypeError("Invalid locale code");
return true;
case ffi::L10nRegistryStatus::EmptyName:
aError.ThrowTypeError("Name cannot be empty.");
return true;
case ffi::L10nRegistryStatus::None:
return false;
}
MOZ_ASSERT_UNREACHABLE("Unknown status");
return false;
}
extern "C" {
nsresult L10nRegistryLoad(const nsACString* aPath,
@ -97,3 +325,5 @@ nsresult L10nRegistryLoadSync(const nsACString* aPath, void** aData,
}
} // extern "C"
} // namespace mozilla::intl

122
intl/l10n/L10nRegistry.h Normal file
View File

@ -0,0 +1,122 @@
/* -*- 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/. */
#ifndef mozilla_intl_l10n_L10nRegistry_h
#define mozilla_intl_l10n_L10nRegistry_h
#include "nsIStreamLoader.h"
#include "nsWrapperCache.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/dom/L10nRegistryBinding.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/intl/FluentBindings.h"
class nsIGlobalObject;
namespace mozilla::intl {
class FluentBundleAsyncIterator final : public nsWrapperCache {
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(FluentBundleAsyncIterator)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(FluentBundleAsyncIterator)
FluentBundleAsyncIterator(
nsIGlobalObject* aGlobal,
UniquePtr<ffi::GeckoFluentBundleAsyncIteratorWrapper> aRaw);
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
nsIGlobalObject* GetParentObject() const { return mGlobal; }
// WebIDL
already_AddRefed<dom::Promise> Next();
already_AddRefed<FluentBundleAsyncIterator> Values();
protected:
~FluentBundleAsyncIterator() = default;
nsCOMPtr<nsIGlobalObject> mGlobal;
UniquePtr<ffi::GeckoFluentBundleAsyncIteratorWrapper> mRaw;
};
class FluentBundleIterator final : public nsWrapperCache {
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(FluentBundleIterator)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(FluentBundleIterator)
FluentBundleIterator(nsIGlobalObject* aGlobal,
UniquePtr<ffi::GeckoFluentBundleIterator> aRaw);
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
nsIGlobalObject* GetParentObject() const { return mGlobal; }
// WebIDL
void Next(dom::FluentBundleIteratorResult& aResult);
already_AddRefed<FluentBundleIterator> Values();
protected:
~FluentBundleIterator() = default;
nsCOMPtr<nsIGlobalObject> mGlobal;
UniquePtr<ffi::GeckoFluentBundleIterator> mRaw;
};
class L10nRegistry final : public nsWrapperCache {
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(L10nRegistry)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(L10nRegistry)
explicit L10nRegistry(nsIGlobalObject* aGlobal);
L10nRegistry(nsIGlobalObject* aGlobal,
RefPtr<const ffi::GeckoL10nRegistry> aRaw);
static already_AddRefed<L10nRegistry> Constructor(
const dom::GlobalObject& aGlobal);
static already_AddRefed<L10nRegistry> GetInstance(
const dom::GlobalObject& aGlobal);
static nsresult Load(const nsACString& aPath,
nsIStreamLoaderObserver* aObserver);
static nsresult LoadSync(const nsACString& aPath, void** aData,
uint64_t* aSize);
void GetAvailableLocales(nsTArray<nsCString>& aRetVal);
void RegisterSources(
const dom::Sequence<OwningNonNull<L10nFileSource>>& aSources);
void UpdateSources(
const dom::Sequence<OwningNonNull<L10nFileSource>>& aSources);
void RemoveSources(const dom::Sequence<nsCString>& aSources);
bool HasSource(const nsACString& aName, ErrorResult& aRv);
already_AddRefed<L10nFileSource> GetSource(const nsACString& aName,
ErrorResult& aRv);
void GetSourceNames(nsTArray<nsCString>& aRetVal);
void ClearSources();
already_AddRefed<FluentBundleIterator> GenerateBundlesSync(
const dom::Sequence<nsCString>& aLocales,
const dom::Sequence<nsCString>& aResourceIds, ErrorResult& aRv);
already_AddRefed<FluentBundleAsyncIterator> GenerateBundles(
const dom::Sequence<nsCString>& aLocales,
const dom::Sequence<nsCString>& aResourceIds, ErrorResult& aRv);
nsIGlobalObject* GetParentObject() const { return mGlobal; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
protected:
virtual ~L10nRegistry() = default;
nsCOMPtr<nsIGlobalObject> mGlobal;
const RefPtr<const ffi::GeckoL10nRegistry> mRaw;
static bool PopulateError(ErrorResult& aError,
ffi::L10nRegistryStatus& aStatus);
};
} // namespace mozilla::intl
#endif

View File

@ -21,6 +21,33 @@ struct RefPtrTraits<intl::ffi::FileSource> {
}
};
template <>
class DefaultDelete<intl::ffi::GeckoFluentBundleIterator> {
public:
void operator()(intl::ffi::GeckoFluentBundleIterator* aPtr) const {
fluent_bundle_iterator_destroy(aPtr);
}
};
template <>
class DefaultDelete<intl::ffi::GeckoFluentBundleAsyncIteratorWrapper> {
public:
void operator()(
intl::ffi::GeckoFluentBundleAsyncIteratorWrapper* aPtr) const {
fluent_bundle_async_iterator_destroy(aPtr);
}
};
template <>
struct RefPtrTraits<intl::ffi::GeckoL10nRegistry> {
static void AddRef(const intl::ffi::GeckoL10nRegistry* aPtr) {
intl::ffi::l10nregistry_addref(aPtr);
}
static void Release(const intl::ffi::GeckoL10nRegistry* aPtr) {
intl::ffi::l10nregistry_release(aPtr);
}
};
} // namespace mozilla
#endif

View File

@ -9,6 +9,7 @@ EXPORTS.mozilla.intl += [
"FluentBindings.h",
"FluentBundle.h",
"FluentResource.h",
"L10nRegistry.h",
"Localization.h",
"RegistryBindings.h",
]

View File

@ -10,6 +10,8 @@ fluent-pseudo = "0.2.3"
intl-memoizer = "0.5"
unic-langid = "0.9"
nsstring = { path = "../../../../xpcom/rust/nsstring" }
cstr = "0.2"
xpcom = { path = "../../../../xpcom/rust/xpcom" }
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
l10nregistry = { git = "https://github.com/zbraniecki/l10nregistry-rs", rev = "92d8fbfbbbdffa2047ce01a935a389eb11031f69" }
fluent-fallback = "0.5"

View File

@ -3,15 +3,18 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::builtins::{FluentDateTime, FluentDateTimeOptions, NumberFormat};
use cstr::cstr;
pub use fluent::{FluentArgs, FluentBundle, FluentError, FluentResource, FluentValue};
use fluent_pseudo::transform_dom;
pub use intl_memoizer::IntlLangMemoizer;
use nsstring::{nsACString, nsCString};
use std::borrow::Cow;
use std::ffi::CStr;
use std::mem;
use std::rc::Rc;
use thin_vec::ThinVec;
use unic_langid::LanguageIdentifier;
use xpcom::interfaces::nsIPrefBranch;
pub type FluentBundleRc = FluentBundle<Rc<FluentResource>>;
@ -42,6 +45,109 @@ fn format_numbers(num: &FluentValue, intls: &IntlLangMemoizer) -> Option<String>
}
}
fn get_string_pref(name: &CStr) -> Option<nsCString> {
let mut value = nsCString::new();
let prefs_service =
xpcom::get_service::<nsIPrefBranch>(cstr!("@mozilla.org/preferences-service;1"))?;
unsafe {
prefs_service
.GetCharPref(name.as_ptr(), &mut *value)
.to_result()
.ok()?;
}
Some(value)
}
fn get_bool_pref(name: &CStr) -> Option<bool> {
let mut value = false;
let prefs_service =
xpcom::get_service::<nsIPrefBranch>(cstr!("@mozilla.org/preferences-service;1"))?;
unsafe {
prefs_service
.GetBoolPref(name.as_ptr(), &mut value)
.to_result()
.ok()?;
}
Some(value)
}
pub fn adapt_bundle_for_gecko(bundle: &mut FluentBundleRc, pseudo_strategy: Option<&nsACString>) {
bundle.set_formatter(Some(format_numbers));
bundle
.add_function("PLATFORM", |_args, _named_args| {
if cfg!(target_os = "linux") {
"linux".into()
} else if cfg!(target_os = "windows") {
"windows".into()
} else if cfg!(target_os = "macos") {
"macos".into()
} else if cfg!(target_os = "android") {
"android".into()
} else {
"other".into()
}
})
.expect("Failed to add a function to the bundle.");
bundle
.add_function("NUMBER", |args, named| {
if let Some(FluentValue::Number(n)) = args.get(0) {
let mut num = n.clone();
num.options.merge(named);
FluentValue::Number(num)
} else {
FluentValue::None
}
})
.expect("Failed to add a function to the bundle.");
bundle
.add_function("DATETIME", |args, named| {
if let Some(FluentValue::Number(n)) = args.get(0) {
let mut options = FluentDateTimeOptions::default();
options.merge(&named);
FluentValue::Custom(Box::new(FluentDateTime::new(n.value, options)))
} else {
FluentValue::None
}
})
.expect("Failed to add a function to the bundle.");
enum PseudoStrategy {
Accented,
Bidi,
None,
}
// This is quirky because we can't coerce Option<&nsACString> and Option<nsCString>
// into bytes easily without allocating.
let strategy_kind = match pseudo_strategy.map(|s| &s[..]) {
Some(b"accented") => PseudoStrategy::Accented,
Some(b"bidi") => PseudoStrategy::Bidi,
_ => {
if let Some(pseudo_strategy) = get_string_pref(cstr!("intl.l10n.pseudo")) {
match &pseudo_strategy[..] {
b"accented" => PseudoStrategy::Accented,
b"bidi" => PseudoStrategy::Bidi,
_ => PseudoStrategy::None,
}
} else {
PseudoStrategy::None
}
}
};
match strategy_kind {
PseudoStrategy::Accented => bundle.set_transform(Some(transform_accented)),
PseudoStrategy::Bidi => bundle.set_transform(Some(transform_bidi)),
PseudoStrategy::None => bundle.set_transform(None),
}
// Temporarily disable bidi isolation due to Microsoft not supporting FSI/PDI.
// See bug 1439018 for details.
let default_use_isolating = false;
let use_isolating =
get_bool_pref(cstr!("intl.l10n.enable-bidi-marks")).unwrap_or(default_use_isolating);
bundle.set_use_isolating(use_isolating);
}
#[no_mangle]
pub unsafe extern "C" fn fluent_bundle_new_single(
locale: &nsACString,
@ -96,51 +202,8 @@ fn fluent_bundle_new_internal(
bundle.set_formatter(Some(format_numbers));
bundle
.add_function("PLATFORM", |_args, _named_args| {
if cfg!(target_os = "linux") {
"linux".into()
} else if cfg!(target_os = "windows") {
"windows".into()
} else if cfg!(target_os = "macos") {
"macos".into()
} else if cfg!(target_os = "android") {
"android".into()
} else {
"other".into()
}
})
.expect("Failed to add a function to the bundle.");
bundle
.add_function("NUMBER", |args, named| {
if let Some(FluentValue::Number(n)) = args.get(0) {
let mut num = n.clone();
num.options.merge(named);
FluentValue::Number(num)
} else {
FluentValue::None
}
})
.expect("Failed to add a function to the bundle.");
bundle
.add_function("DATETIME", |args, named| {
if let Some(FluentValue::Number(n)) = args.get(0) {
let mut options = FluentDateTimeOptions::default();
options.merge(&named);
FluentValue::Custom(Box::new(FluentDateTime::new(n.value, options)))
} else {
FluentValue::None
}
})
.expect("Failed to add a function to the bundle.");
adapt_bundle_for_gecko(&mut bundle, Some(pseudo_strategy));
if !pseudo_strategy.is_empty() {
match &pseudo_strategy[..] {
b"accented" => bundle.set_transform(Some(transform_accented)),
b"bidi" => bundle.set_transform(Some(transform_bidi)),
_ => bundle.set_transform(None),
}
}
Box::new(bundle)
}

View File

@ -6,6 +6,7 @@ edition = "2018"
[dependencies]
futures-channel = "0.3"
futures = "0.3"
libc = "0.2"
cstr = "0.2"
log = "0.4"
@ -18,3 +19,5 @@ thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
async-trait = "0.1"
moz_task = { path = "../../../../xpcom/rust/moz_task" }
xpcom = { path = "../../../../xpcom/rust/xpcom" }
fluent-ffi = { path = "../fluent-ffi" }
fluent-fallback = "0.5"

View File

@ -3,7 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cstr::cstr;
use l10nregistry::{env::ErrorReporter, errors::L10nRegistryError};
use l10nregistry::{
env::ErrorReporter,
errors::{L10nRegistryError, L10nRegistrySetupError},
};
use log::warn;
use nserror::{nsresult, NS_ERROR_NOT_AVAILABLE};
use nsstring::nsString;
@ -16,6 +19,24 @@ use xpcom::interfaces;
#[derive(Clone)]
pub struct GeckoEnvironment;
impl GeckoEnvironment {
pub fn report_l10nregistry_setup_error(error: &L10nRegistrySetupError) {
warn!("L10nRegistry setup error: {}", error);
let result = log_simple_console_error(
&error.to_string(),
cstr!("l10n"),
false,
true,
None,
(0, 0),
interfaces::nsIScriptError::errorFlag as u32,
);
if let Err(err) = result {
warn!("Error while reporting an error: {}", err);
}
}
}
impl ErrorReporter for GeckoEnvironment {
fn report_errors(&self, errors: Vec<L10nRegistryError>) {
for error in errors {

View File

@ -5,4 +5,5 @@
mod env;
mod fetcher;
pub mod load;
mod registry;
mod source;

View File

@ -0,0 +1,339 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use fluent_ffi::{adapt_bundle_for_gecko, FluentBundleRc};
use nsstring::{nsACString, nsCString};
use std::mem;
use std::rc::Rc;
use thin_vec::ThinVec;
use crate::{env::GeckoEnvironment, fetcher::GeckoFileFetcher};
use fluent_fallback::generator::BundleGenerator;
use futures_channel::mpsc::{unbounded, UnboundedSender};
pub use l10nregistry::{
errors::L10nRegistrySetupError,
registry::{BundleAdapter, GenerateBundles, GenerateBundlesSync, L10nRegistry},
source::FileSource,
};
use unic_langid::LanguageIdentifier;
use xpcom::RefPtr;
#[derive(Clone)]
pub struct GeckoBundleAdapter {
use_isolating: bool,
}
impl Default for GeckoBundleAdapter {
fn default() -> Self {
Self {
use_isolating: true,
}
}
}
impl BundleAdapter for GeckoBundleAdapter {
fn adapt_bundle(&self, bundle: &mut l10nregistry::fluent::FluentBundle) {
bundle.set_use_isolating(self.use_isolating);
adapt_bundle_for_gecko(bundle, None);
}
}
thread_local!(static L10N_REGISTRY: Rc<GeckoL10nRegistry> = {
let env = GeckoEnvironment;
let mut reg = L10nRegistry::with_provider(env);
let packaged_locales = vec!["en-US".parse().unwrap()];
let toolkit_fs = FileSource::new(
"0-toolkit".to_owned(),
packaged_locales.clone(),
"resource://gre/localization/{locale}/".to_owned(),
Default::default(),
GeckoFileFetcher,
);
let browser_fs = FileSource::new(
"5-browser".to_owned(),
packaged_locales,
"resource://app/localization/{locale}/".to_owned(),
Default::default(),
GeckoFileFetcher,
);
let _ = reg.set_adapt_bundle(GeckoBundleAdapter::default()).report_error();
let _ = reg.register_sources(vec![toolkit_fs, browser_fs]).report_error();
Rc::new(reg)
});
pub type GeckoL10nRegistry = L10nRegistry<GeckoEnvironment, GeckoBundleAdapter>;
pub type GeckoFluentBundleIterator = GenerateBundlesSync<GeckoEnvironment, GeckoBundleAdapter>;
trait GeckoReportError<V, E> {
fn report_error(self) -> Result<V, E>;
}
impl<V> GeckoReportError<V, L10nRegistrySetupError> for Result<V, L10nRegistrySetupError> {
fn report_error(self) -> Self {
if let Err(ref err) = self {
GeckoEnvironment::report_l10nregistry_setup_error(err);
}
self
}
}
pub fn get_l10n_registry() -> Rc<GeckoL10nRegistry> {
L10N_REGISTRY.with(|reg| reg.clone())
}
#[repr(C)]
pub enum L10nRegistryStatus {
None,
EmptyName,
InvalidLocaleCode,
}
#[no_mangle]
pub extern "C" fn l10nregistry_new() -> *const GeckoL10nRegistry {
let env = GeckoEnvironment;
let mut reg = L10nRegistry::with_provider(env);
let _ = reg
.set_adapt_bundle(GeckoBundleAdapter::default())
.report_error();
Rc::into_raw(Rc::new(reg))
}
#[no_mangle]
pub extern "C" fn l10nregistry_instance_get() -> *const GeckoL10nRegistry {
let reg = get_l10n_registry();
Rc::into_raw(reg)
}
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_addref(reg: &GeckoL10nRegistry) {
let raw = Rc::from_raw(reg);
mem::forget(Rc::clone(&raw));
mem::forget(raw);
}
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_release(reg: &GeckoL10nRegistry) {
let _ = Rc::from_raw(reg);
}
#[no_mangle]
pub extern "C" fn l10nregistry_get_available_locales(
reg: &GeckoL10nRegistry,
result: &mut ThinVec<nsCString>,
) {
if let Ok(locales) = reg.get_available_locales().report_error() {
result.extend(locales.into_iter().map(|locale| locale.to_string().into()));
}
}
#[no_mangle]
pub extern "C" fn l10nregistry_register_sources(
reg: &GeckoL10nRegistry,
sources: &ThinVec<&FileSource>,
) {
let _ = reg
.register_sources(sources.iter().map(|&s| s.clone()).collect())
.report_error();
}
#[no_mangle]
pub extern "C" fn l10nregistry_update_sources(
reg: &GeckoL10nRegistry,
sources: &mut ThinVec<&FileSource>,
) {
let _ = reg
.update_sources(sources.iter().map(|&s| s.clone()).collect())
.report_error();
}
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_remove_sources(
reg: &GeckoL10nRegistry,
sources_elements: *const nsCString,
sources_length: usize,
) {
if sources_elements.is_null() {
return;
}
let sources = std::slice::from_raw_parts(sources_elements, sources_length);
let _ = reg.remove_sources(sources.to_vec()).report_error();
}
#[no_mangle]
pub extern "C" fn l10nregistry_has_source(
reg: &GeckoL10nRegistry,
name: &nsACString,
status: &mut L10nRegistryStatus,
) -> bool {
if name.is_empty() {
*status = L10nRegistryStatus::EmptyName;
return false;
}
*status = L10nRegistryStatus::None;
reg.has_source(&name.to_utf8())
.report_error()
.unwrap_or(false)
}
#[no_mangle]
pub extern "C" fn l10nregistry_get_source(
reg: &GeckoL10nRegistry,
name: &nsACString,
status: &mut L10nRegistryStatus,
) -> *mut FileSource {
if name.is_empty() {
*status = L10nRegistryStatus::EmptyName;
return std::ptr::null_mut();
}
*status = L10nRegistryStatus::None;
if let Ok(Some(source)) = reg.get_source(&name.to_utf8()).report_error() {
Box::into_raw(Box::new(source))
} else {
std::ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn l10nregistry_clear_sources(reg: &GeckoL10nRegistry) {
let _ = reg.clear_sources().report_error();
}
#[no_mangle]
pub extern "C" fn l10nregistry_get_source_names(
reg: &GeckoL10nRegistry,
result: &mut ThinVec<nsCString>,
) {
if let Ok(names) = reg.get_source_names().report_error() {
result.extend(names.into_iter().map(|name| nsCString::from(name)));
}
}
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_generate_bundles_sync(
reg: &GeckoL10nRegistry,
locales_elements: *const nsCString,
locales_length: usize,
res_ids_elements: *const nsCString,
res_ids_length: usize,
status: &mut L10nRegistryStatus,
) -> *mut GeckoFluentBundleIterator {
let locales = std::slice::from_raw_parts(locales_elements, locales_length);
let res_ids = std::slice::from_raw_parts(res_ids_elements, res_ids_length);
let locales: Result<Vec<LanguageIdentifier>, _> =
locales.into_iter().map(|s| s.to_utf8().parse()).collect();
match locales {
Ok(locales) => {
*status = L10nRegistryStatus::None;
let res_ids = res_ids.into_iter().map(|s| s.to_string()).collect();
let iter = reg.bundles_iter(locales.into_iter(), res_ids);
Box::into_raw(Box::new(iter))
}
Err(_) => {
*status = L10nRegistryStatus::InvalidLocaleCode;
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn fluent_bundle_iterator_destroy(iter: *mut GeckoFluentBundleIterator) {
let _ = Box::from_raw(iter);
}
#[no_mangle]
pub extern "C" fn fluent_bundle_iterator_next(
iter: &mut GeckoFluentBundleIterator,
) -> *mut FluentBundleRc {
if let Some(Ok(result)) = iter.next() {
Box::into_raw(Box::new(result))
} else {
std::ptr::null_mut()
}
}
pub struct NextRequest {
promise: RefPtr<xpcom::Promise>,
// Ownership is transferred here.
callback: unsafe extern "C" fn(&xpcom::Promise, *mut FluentBundleRc),
}
pub struct GeckoFluentBundleAsyncIteratorWrapper(UnboundedSender<NextRequest>);
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_generate_bundles(
reg: &GeckoL10nRegistry,
locales_elements: *const nsCString,
locales_length: usize,
res_ids_elements: *const nsCString,
res_ids_length: usize,
status: &mut L10nRegistryStatus,
) -> *mut GeckoFluentBundleAsyncIteratorWrapper {
let locales = std::slice::from_raw_parts(locales_elements, locales_length);
let res_ids = std::slice::from_raw_parts(res_ids_elements, res_ids_length);
let locales: Result<Vec<LanguageIdentifier>, _> =
locales.into_iter().map(|s| s.to_utf8().parse()).collect();
match locales {
Ok(locales) => {
*status = L10nRegistryStatus::None;
let res_ids = res_ids.into_iter().map(|s| s.to_string()).collect();
let mut iter = reg.bundles_stream(locales.into_iter(), res_ids);
// Immediately spawn the task which will handle the async calls, and use an `UnboundedSender`
// to send callbacks for specific `next()` calls to it.
let (sender, mut receiver) = unbounded::<NextRequest>();
moz_task::spawn_current_thread(async move {
use futures::StreamExt;
while let Some(req) = receiver.next().await {
let result = match iter.next().await {
Some(Ok(result)) => Box::into_raw(Box::new(result)),
_ => std::ptr::null_mut(),
};
(req.callback)(&req.promise, result);
}
})
.expect("Failed to spawn a task");
let iter = GeckoFluentBundleAsyncIteratorWrapper(sender);
Box::into_raw(Box::new(iter))
}
Err(_) => {
*status = L10nRegistryStatus::InvalidLocaleCode;
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn fluent_bundle_async_iterator_destroy(
iter: *mut GeckoFluentBundleAsyncIteratorWrapper,
) {
let _ = Box::from_raw(iter);
}
#[no_mangle]
pub extern "C" fn fluent_bundle_async_iterator_next(
iter: &GeckoFluentBundleAsyncIteratorWrapper,
promise: &xpcom::Promise,
callback: extern "C" fn(&xpcom::Promise, *mut FluentBundleRc),
) {
if iter
.0
.unbounded_send(NextRequest {
promise: RefPtr::new(promise),
callback,
})
.is_err()
{
callback(promise, std::ptr::null_mut());
}
}

View File

@ -156,7 +156,13 @@ pub extern "C" fn l10nfilesource_new_mock(
.map(|mock| (mock.path.to_string(), mock.source.to_string()))
.collect();
let fetcher = MockFileFetcher::new(fs);
let mut source = FileSource::new(name.to_string(), locales, pre_path.to_string(), Default::default(), fetcher);
let mut source = FileSource::new(
name.to_string(),
locales,
pre_path.to_string(),
Default::default(),
fetcher,
);
source.set_reporter(GeckoEnvironment);
*status = L10nFileSourceStatus::None;