mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 12:20:56 +00:00
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:
parent
36a07a5f2a
commit
f42090a25a
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
122
intl/l10n/L10nRegistry.h
Normal 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
|
@ -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
|
||||
|
@ -9,6 +9,7 @@ EXPORTS.mozilla.intl += [
|
||||
"FluentBindings.h",
|
||||
"FluentBundle.h",
|
||||
"FluentResource.h",
|
||||
"L10nRegistry.h",
|
||||
"Localization.h",
|
||||
"RegistryBindings.h",
|
||||
]
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -5,4 +5,5 @@
|
||||
mod env;
|
||||
mod fetcher;
|
||||
pub mod load;
|
||||
mod registry;
|
||||
mod source;
|
||||
|
339
intl/l10n/rust/l10nregistry-ffi/src/registry.rs
Normal file
339
intl/l10n/rust/l10nregistry-ffi/src/registry.rs
Normal 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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user