mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1672317 - [l10nfilesource] part3: Introduce L10nFileSource backed by l10nregistry-rs. r=nika,emilio
Depends on D102102 Differential Revision: https://phabricator.services.mozilla.com/D103002
This commit is contained in:
parent
e0ee86ae33
commit
f23494c7e7
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -2656,10 +2656,19 @@ dependencies = [
|
||||
name = "l10nregistry-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cstr",
|
||||
"fluent",
|
||||
"futures-channel",
|
||||
"l10nregistry 0.2.0 (git+https://github.com/zbraniecki/l10nregistry-rs?rev=92d8fbfbbbdffa2047ce01a935a389eb11031f69)",
|
||||
"libc",
|
||||
"log",
|
||||
"moz_task",
|
||||
"nserror",
|
||||
"nsstring",
|
||||
"thin-vec",
|
||||
"unic-langid",
|
||||
"xpcom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -440,6 +440,11 @@ DOMInterfaces = {
|
||||
'concrete': False,
|
||||
},
|
||||
|
||||
'L10nFileSource': {
|
||||
'headerFile': 'mozilla/intl/FileSource.h',
|
||||
'nativeType': 'mozilla::intl::L10nFileSource',
|
||||
},
|
||||
|
||||
'LegacyMozTCPSocket': {
|
||||
'headerFile': 'TCPSocket.h',
|
||||
'wrapperCache': False,
|
||||
|
89
dom/chrome-webidl/L10nRegistry.webidl
Normal file
89
dom/chrome-webidl/L10nRegistry.webidl
Normal file
@ -0,0 +1,89 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
dictionary L10nFileSourceMockFile {
|
||||
required UTF8String path;
|
||||
required UTF8String source;
|
||||
};
|
||||
|
||||
enum L10nFileSourceHasFileStatus {
|
||||
"present",
|
||||
"missing",
|
||||
"unknown"
|
||||
};
|
||||
|
||||
/**
|
||||
* The interface represents a single source location for
|
||||
* the registry.
|
||||
*
|
||||
* Examples of sources are:
|
||||
* * Toolkit omni.ja Localization Directory
|
||||
* * Browser omni.ja Localization Directory
|
||||
* * Language Pack Toolkit Localization Directory
|
||||
* * Language Pack Browser Localization Directory
|
||||
*/
|
||||
[ChromeOnly,
|
||||
Exposed=Window]
|
||||
interface L10nFileSource {
|
||||
/**
|
||||
* Optional argument `index` can be used to provide a list
|
||||
* of files available in the source.
|
||||
*
|
||||
* This is particularly useful for custom file sources which
|
||||
* provide a small number of known override files allowing the
|
||||
* registry to avoid trying I/O on the source for all
|
||||
* files not available in the source.
|
||||
*/
|
||||
[Throws]
|
||||
constructor(UTF8String name, sequence<UTF8String> locales, UTF8String prePath, optional sequence<UTF8String> index);
|
||||
|
||||
/**
|
||||
* Tests may want to introduce custom file sources and
|
||||
* register them in a custom `L10nRegistry` to mock
|
||||
* behavior of using localization with test file sources.
|
||||
*
|
||||
* # Example:
|
||||
*
|
||||
* ```
|
||||
* let fs = [
|
||||
* {
|
||||
* "path": "/localization/en-US/test.ftl",
|
||||
* "source": "key = Hello World!",
|
||||
* }
|
||||
* ];
|
||||
* let mockSource = L10nFileSource.createMock("mock", ["en-US"], "/localization/{locale}/", fs);
|
||||
*
|
||||
* let reg = new L10nRegistry();
|
||||
* reg.registerSources([mockSource]);
|
||||
*
|
||||
* let loc = new Localization(["test.ftl"], reg);
|
||||
* let value = await loc.formatValue("key");
|
||||
* assert(value, "Hello World!");
|
||||
* ```
|
||||
*/
|
||||
[Throws]
|
||||
static L10nFileSource createMock(UTF8String name, sequence<UTF8String> locales, UTF8String prePath, sequence<L10nFileSourceMockFile> fs);
|
||||
|
||||
readonly attribute UTF8String name;
|
||||
[Pure, Cached, Frozen]
|
||||
readonly attribute sequence<UTF8String> locales;
|
||||
/**
|
||||
* `prePath` is the root path used together with a relative path to construct the full path used to retrieve a file
|
||||
* out of a file source.
|
||||
*
|
||||
* The `prePath` will usually contain a placeholder `{locale}` which gets replaced with a given locale.
|
||||
*/
|
||||
readonly attribute UTF8String prePath;
|
||||
[Pure, Cached, Frozen]
|
||||
readonly attribute sequence<UTF8String>? index;
|
||||
|
||||
[Throws]
|
||||
L10nFileSourceHasFileStatus hasFile(UTF8String locale, UTF8String path);
|
||||
[Throws]
|
||||
Promise<FluentResource?> fetchFile(UTF8String locale, UTF8String path);
|
||||
[Throws]
|
||||
FluentResource? fetchFileSync(UTF8String locale, UTF8String path);
|
||||
};
|
||||
|
@ -56,6 +56,7 @@ WEBIDL_FILES = [
|
||||
"JSProcessActor.webidl",
|
||||
"JSWindowActor.webidl",
|
||||
"L10nOverlays.webidl",
|
||||
"L10nRegistry.webidl",
|
||||
"MatchGlob.webidl",
|
||||
"MatchPattern.webidl",
|
||||
"MediaController.webidl",
|
||||
|
203
intl/l10n/FileSource.cpp
Normal file
203
intl/l10n/FileSource.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#include "FileSource.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
namespace mozilla::intl {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(L10nFileSource, mGlobal)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(L10nFileSource, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(L10nFileSource, Release)
|
||||
|
||||
L10nFileSource::L10nFileSource(RefPtr<const ffi::FileSource> aRaw,
|
||||
nsIGlobalObject* aGlobal)
|
||||
: mGlobal(aGlobal), mRaw(std::move(aRaw)) {}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<L10nFileSource> L10nFileSource::Create(
|
||||
const nsACString& aName, const nsTArray<nsCString>& aLocales,
|
||||
const nsACString& aPrePath, ErrorResult& aRv) {
|
||||
ffi::L10nFileSourceStatus status;
|
||||
|
||||
RefPtr<const ffi::FileSource> raw(dont_AddRef(
|
||||
ffi::l10nfilesource_new(&aName, &aLocales, &aPrePath, &status)));
|
||||
|
||||
if (PopulateError(aRv, status)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return MakeAndAddRef<L10nFileSource>(std::move(raw));
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<L10nFileSource> L10nFileSource::Constructor(
|
||||
const GlobalObject& aGlobal, const nsACString& aName,
|
||||
const nsTArray<nsCString>& aLocales, const nsACString& aPrePath,
|
||||
const Optional<Sequence<nsCString>>& aIndex, ErrorResult& aRv) {
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
|
||||
ffi::L10nFileSourceStatus status;
|
||||
|
||||
RefPtr<const ffi::FileSource> raw;
|
||||
if (aIndex.WasPassed()) {
|
||||
raw = dont_AddRef(ffi::l10nfilesource_new_with_index(
|
||||
&aName, &aLocales, &aPrePath, aIndex.Value().Elements(),
|
||||
aIndex.Value().Length(), &status));
|
||||
} else {
|
||||
raw = dont_AddRef(
|
||||
ffi::l10nfilesource_new(&aName, &aLocales, &aPrePath, &status));
|
||||
}
|
||||
|
||||
if (PopulateError(aRv, status)) {
|
||||
return nullptr;
|
||||
}
|
||||
return MakeAndAddRef<L10nFileSource>(std::move(raw), global);
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<L10nFileSource> L10nFileSource::CreateMock(
|
||||
const GlobalObject& aGlobal, const nsACString& aName,
|
||||
const nsTArray<nsCString>& aLocales, const nsACString& aPrePath,
|
||||
const nsTArray<L10nFileSourceMockFile>& aFS, ErrorResult& aRv) {
|
||||
nsTArray<ffi::L10nFileSourceMockFile> fs(aFS.Length());
|
||||
for (const auto& file : aFS) {
|
||||
auto f = fs.AppendElement();
|
||||
f->path = file.mPath;
|
||||
f->source = file.mSource;
|
||||
}
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
|
||||
ffi::L10nFileSourceStatus status;
|
||||
|
||||
RefPtr<const ffi::FileSource> raw(dont_AddRef(ffi::l10nfilesource_new_mock(
|
||||
&aName, &aLocales, &aPrePath, &fs, &status)));
|
||||
|
||||
if (PopulateError(aRv, status)) {
|
||||
return nullptr;
|
||||
}
|
||||
return MakeAndAddRef<L10nFileSource>(std::move(raw), global);
|
||||
}
|
||||
|
||||
JSObject* L10nFileSource::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return L10nFileSource_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void L10nFileSource::GetName(nsCString& aRetVal) {
|
||||
ffi::l10nfilesource_get_name(mRaw.get(), &aRetVal);
|
||||
}
|
||||
|
||||
void L10nFileSource::GetLocales(nsTArray<nsCString>& aRetVal) {
|
||||
ffi::l10nfilesource_get_locales(mRaw.get(), &aRetVal);
|
||||
}
|
||||
|
||||
void L10nFileSource::GetPrePath(nsCString& aRetVal) {
|
||||
ffi::l10nfilesource_get_prepath(mRaw.get(), &aRetVal);
|
||||
}
|
||||
|
||||
void L10nFileSource::GetIndex(Nullable<nsTArray<nsCString>>& aRetVal) {
|
||||
bool hasIndex =
|
||||
ffi::l10nfilesource_get_index(mRaw.get(), &aRetVal.SetValue());
|
||||
if (!hasIndex) {
|
||||
aRetVal.SetNull();
|
||||
}
|
||||
}
|
||||
|
||||
L10nFileSourceHasFileStatus L10nFileSource::HasFile(const nsACString& aLocale,
|
||||
const nsACString& aPath,
|
||||
ErrorResult& aRv) {
|
||||
ffi::L10nFileSourceStatus status;
|
||||
|
||||
bool isPresent = false;
|
||||
bool hasValue = ffi::l10nfilesource_has_file(mRaw.get(), &aLocale, &aPath,
|
||||
&status, &isPresent);
|
||||
|
||||
if (!PopulateError(aRv, status) && hasValue) {
|
||||
if (isPresent) {
|
||||
return L10nFileSourceHasFileStatus::Present;
|
||||
}
|
||||
|
||||
return L10nFileSourceHasFileStatus::Missing;
|
||||
}
|
||||
return L10nFileSourceHasFileStatus::Unknown;
|
||||
}
|
||||
|
||||
already_AddRefed<FluentResource> L10nFileSource::FetchFileSync(
|
||||
const nsACString& aLocale, const nsACString& aPath, ErrorResult& aRv) {
|
||||
ffi::L10nFileSourceStatus status;
|
||||
|
||||
RefPtr<const ffi::FluentResource> raw =
|
||||
dont_AddRef(ffi::l10nfilesource_fetch_file_sync(mRaw.get(), &aLocale,
|
||||
&aPath, &status));
|
||||
|
||||
if (!PopulateError(aRv, status) && raw) {
|
||||
return MakeAndAddRef<FluentResource>(mGlobal, raw);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> L10nFileSource::FetchFile(const nsACString& aLocale,
|
||||
const nsACString& aPath,
|
||||
ErrorResult& aRv) {
|
||||
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ffi::L10nFileSourceStatus status;
|
||||
|
||||
ffi::l10nfilesource_fetch_file(
|
||||
mRaw.get(), &aLocale, &aPath, promise,
|
||||
[](const Promise* aPromise, const ffi::FluentResource* aRes) {
|
||||
Promise* promise = const_cast<Promise*>(aPromise);
|
||||
|
||||
if (aRes) {
|
||||
nsIGlobalObject* global = promise->GetGlobalObject();
|
||||
RefPtr<FluentResource> res = new FluentResource(global, aRes);
|
||||
promise->MaybeResolve(res);
|
||||
} else {
|
||||
promise->MaybeResolve(JS::NullHandleValue);
|
||||
}
|
||||
},
|
||||
&status);
|
||||
|
||||
if (PopulateError(aRv, status)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool L10nFileSource::PopulateError(ErrorResult& aError,
|
||||
ffi::L10nFileSourceStatus& aStatus) {
|
||||
switch (aStatus) {
|
||||
case ffi::L10nFileSourceStatus::InvalidLocaleCode:
|
||||
aError.ThrowTypeError("Invalid locale code");
|
||||
return true;
|
||||
case ffi::L10nFileSourceStatus::EmptyName:
|
||||
aError.ThrowTypeError("Name cannot be empty.");
|
||||
return true;
|
||||
case ffi::L10nFileSourceStatus::EmptyPrePath:
|
||||
aError.ThrowTypeError("prePath cannot be empty.");
|
||||
return true;
|
||||
case ffi::L10nFileSourceStatus::EmptyResId:
|
||||
aError.ThrowTypeError("resId cannot be empty.");
|
||||
return true;
|
||||
|
||||
case ffi::L10nFileSourceStatus::None:
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT_UNREACHABLE("Unknown status");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mozilla::intl
|
72
intl/l10n/FileSource.h
Normal file
72
intl/l10n/FileSource.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* -*- 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_FileSource_h
|
||||
#define mozilla_intl_l10n_FileSource_h
|
||||
|
||||
#include "nsWrapperCache.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/L10nRegistryBinding.h"
|
||||
#include "mozilla/dom/FluentBinding.h"
|
||||
#include "mozilla/intl/RegistryBindings.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
|
||||
namespace mozilla::intl {
|
||||
|
||||
class L10nFileSource : public nsWrapperCache {
|
||||
public:
|
||||
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(L10nFileSource)
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(L10nFileSource)
|
||||
|
||||
explicit L10nFileSource(RefPtr<const ffi::FileSource> aRaw,
|
||||
nsIGlobalObject* aGlobal = nullptr);
|
||||
|
||||
static already_AddRefed<L10nFileSource> Create(
|
||||
const nsACString& aName, const nsTArray<nsCString>& aLocales,
|
||||
const nsACString& aPrePath, ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<L10nFileSource> Constructor(
|
||||
const dom::GlobalObject& aGlobal, const nsACString& aName,
|
||||
const nsTArray<nsCString>& aLocales, const nsACString& aPrePath,
|
||||
const dom::Optional<dom::Sequence<nsCString>>& aIndex, ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<L10nFileSource> CreateMock(
|
||||
const dom::GlobalObject& aGlobal, const nsACString& aName,
|
||||
const nsTArray<nsCString>& aLocales, const nsACString& aPrePath,
|
||||
const nsTArray<dom::L10nFileSourceMockFile>& aFS, ErrorResult& aRv);
|
||||
|
||||
nsIGlobalObject* GetParentObject() const { return mGlobal; }
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
void GetName(nsCString& aRetVal);
|
||||
void GetLocales(nsTArray<nsCString>& aRetVal);
|
||||
void GetPrePath(nsCString& aRetVal);
|
||||
void GetIndex(dom::Nullable<nsTArray<nsCString>>& aRetVal);
|
||||
|
||||
dom::L10nFileSourceHasFileStatus HasFile(const nsACString& aLocale,
|
||||
const nsACString& aPath,
|
||||
ErrorResult& aRv);
|
||||
already_AddRefed<dom::Promise> FetchFile(const nsACString& aLocale,
|
||||
const nsACString& aPath,
|
||||
ErrorResult& aRv);
|
||||
already_AddRefed<FluentResource> FetchFileSync(const nsACString& aLocale,
|
||||
const nsACString& aPath,
|
||||
ErrorResult& aRv);
|
||||
|
||||
protected:
|
||||
virtual ~L10nFileSource() = default;
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
const RefPtr<const ffi::FileSource> mRaw;
|
||||
static bool PopulateError(ErrorResult& aError,
|
||||
ffi::L10nFileSourceStatus& aStatus);
|
||||
};
|
||||
|
||||
} // namespace mozilla::intl
|
||||
|
||||
#endif
|
@ -17,10 +17,13 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentResource, mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentResource, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentResource, Release)
|
||||
|
||||
FluentResource::FluentResource(nsISupports* aParent,
|
||||
const ffi::FluentResource* aRaw)
|
||||
: mParent(aParent), mRaw(std::move(aRaw)), mHasErrors(false) {}
|
||||
|
||||
FluentResource::FluentResource(nsISupports* aParent, const nsACString& aSource)
|
||||
: mParent(aParent),
|
||||
mRaw(dont_AddRef(ffi::fluent_resource_new(&aSource, &mHasErrors))) {
|
||||
MOZ_COUNT_CTOR(FluentResource);
|
||||
: mParent(aParent), mHasErrors(false) {
|
||||
mRaw = dont_AddRef(ffi::fluent_resource_new(&aSource, &mHasErrors));
|
||||
}
|
||||
|
||||
already_AddRefed<FluentResource> FluentResource::Constructor(
|
||||
@ -41,7 +44,5 @@ JSObject* FluentResource::WrapObject(JSContext* aCx,
|
||||
return FluentResource_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
FluentResource::~FluentResource() { MOZ_COUNT_DTOR(FluentResource); };
|
||||
|
||||
} // namespace intl
|
||||
} // namespace mozilla
|
||||
|
@ -21,7 +21,8 @@ class FluentResource : public nsWrapperCache {
|
||||
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(FluentResource)
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(FluentResource)
|
||||
|
||||
explicit FluentResource(nsISupports* aParent, const nsACString& aSource);
|
||||
FluentResource(nsISupports* aParent, const ffi::FluentResource* aRaw);
|
||||
FluentResource(nsISupports* aParent, const nsACString& aSource);
|
||||
|
||||
static already_AddRefed<FluentResource> Constructor(
|
||||
const dom::GlobalObject& aGlobal, const nsACString& aSource);
|
||||
@ -33,10 +34,10 @@ class FluentResource : public nsWrapperCache {
|
||||
const ffi::FluentResource* Raw() const { return mRaw; }
|
||||
|
||||
protected:
|
||||
virtual ~FluentResource();
|
||||
virtual ~FluentResource() = default;
|
||||
|
||||
nsCOMPtr<nsISupports> mParent;
|
||||
const RefPtr<const ffi::FluentResource> mRaw;
|
||||
RefPtr<const ffi::FluentResource> mRaw;
|
||||
bool mHasErrors;
|
||||
};
|
||||
|
||||
|
@ -13,63 +13,18 @@
|
||||
namespace mozilla {
|
||||
namespace intl {
|
||||
|
||||
class ResourceLoader final : public nsIStreamLoaderObserver {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSISTREAMLOADEROBSERVER
|
||||
|
||||
typedef nsresult (*Callback)(void* aClosure, nsACString* aString,
|
||||
nsresult aSuccess);
|
||||
|
||||
ResourceLoader(Callback aCallback, void* aClosure)
|
||||
: mCallback(aCallback), mClosure(aClosure) {}
|
||||
|
||||
protected:
|
||||
~ResourceLoader() = default;
|
||||
|
||||
private:
|
||||
Callback mCallback;
|
||||
void* mClosure;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ResourceLoader, nsIStreamLoaderObserver)
|
||||
|
||||
// nsIStreamLoaderObserver
|
||||
NS_IMETHODIMP
|
||||
ResourceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
|
||||
nsISupports* aContext, nsresult aStatus,
|
||||
uint32_t aStringLen, const uint8_t* aString) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (NS_FAILED(aStatus)) {
|
||||
mCallback(mClosure, nullptr, aStatus);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCString data;
|
||||
data.Adopt(reinterpret_cast<char*>(const_cast<uint8_t*>(aString)),
|
||||
aStringLen);
|
||||
mCallback(mClosure, &data, NS_OK);
|
||||
|
||||
return NS_SUCCESS_ADOPTED_DATA;
|
||||
}
|
||||
|
||||
class L10nRegistry {
|
||||
public:
|
||||
static nsresult Load(const nsACString& aPath,
|
||||
ResourceLoader::Callback aCallback, void* aClosure) {
|
||||
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<ResourceLoader> listener =
|
||||
MakeRefPtr<ResourceLoader>(aCallback, aClosure);
|
||||
|
||||
// TODO: What is the lifetime requirement for loader?
|
||||
RefPtr<nsIStreamLoader> loader;
|
||||
rv = NS_NewStreamLoader(
|
||||
getter_AddRefs(loader), uri, listener,
|
||||
getter_AddRefs(loader), uri, aObserver,
|
||||
nsContentUtils::GetSystemPrincipal(),
|
||||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
|
||||
nsIContentPolicy::TYPE_OTHER);
|
||||
@ -77,7 +32,8 @@ class L10nRegistry {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static nsresult LoadSync(const nsACString& aPath, nsACString& aRetVal) {
|
||||
static nsresult LoadSync(const nsACString& aPath, void** aData,
|
||||
uint64_t* aSize) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath);
|
||||
@ -87,7 +43,9 @@ class L10nRegistry {
|
||||
|
||||
auto result = URLPreloader::ReadURI(uri);
|
||||
if (result.isOk()) {
|
||||
aRetVal = result.unwrap();
|
||||
auto uri = result.unwrap();
|
||||
*aData = ToNewCString(uri);
|
||||
*aSize = uri.Length();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -104,14 +62,14 @@ class L10nRegistry {
|
||||
nullptr, /* aPerformanceStorage */
|
||||
nullptr, /* aLoadGroup */
|
||||
nullptr, /* aCallbacks */
|
||||
nsIRequest::LOAD_BACKGROUND);
|
||||
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_ReadInputStreamToString(input, aRetVal, -1);
|
||||
return NS_ReadInputStreamToBuffer(input, aData, -1, aSize);
|
||||
}
|
||||
};
|
||||
|
||||
@ -120,16 +78,22 @@ class L10nRegistry {
|
||||
|
||||
extern "C" {
|
||||
nsresult L10nRegistryLoad(const nsACString* aPath,
|
||||
mozilla::intl::ResourceLoader::Callback aCallback,
|
||||
void* aClosure) {
|
||||
if (!aPath || !aCallback) {
|
||||
const nsIStreamLoaderObserver* aObserver) {
|
||||
if (!aPath || !aObserver) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return mozilla::intl::L10nRegistry::Load(*aPath, aCallback, aClosure);
|
||||
return mozilla::intl::L10nRegistry::Load(
|
||||
*aPath, const_cast<nsIStreamLoaderObserver*>(aObserver));
|
||||
}
|
||||
|
||||
nsresult L10nRegistryLoadSync(const nsACString* aPath, nsACString* aRetVal) {
|
||||
return mozilla::intl::L10nRegistry::LoadSync(*aPath, *aRetVal);
|
||||
}
|
||||
nsresult L10nRegistryLoadSync(const nsACString* aPath, void** aData,
|
||||
uint64_t* aSize) {
|
||||
if (!aPath || !aData || !aSize) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return mozilla::intl::L10nRegistry::LoadSync(*aPath, aData, aSize);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
26
intl/l10n/RegistryBindings.h
Normal file
26
intl/l10n/RegistryBindings.h
Normal file
@ -0,0 +1,26 @@
|
||||
/* 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_RegistryBindings_h
|
||||
#define mozilla_intl_l10n_RegistryBindings_h
|
||||
|
||||
#include "mozilla/intl/l10nregistry_ffi_generated.h"
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
template <>
|
||||
struct RefPtrTraits<intl::ffi::FileSource> {
|
||||
static void AddRef(const intl::ffi::FileSource* aPtr) {
|
||||
intl::ffi::l10nfilesource_addref(aPtr);
|
||||
}
|
||||
static void Release(const intl::ffi::FileSource* aPtr) {
|
||||
intl::ffi::l10nfilesource_release(aPtr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
@ -5,13 +5,16 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXPORTS.mozilla.intl += [
|
||||
"FileSource.h",
|
||||
"FluentBindings.h",
|
||||
"FluentBundle.h",
|
||||
"FluentResource.h",
|
||||
"Localization.h",
|
||||
"RegistryBindings.h",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"FileSource.cpp",
|
||||
"FluentBundle.cpp",
|
||||
"FluentResource.cpp",
|
||||
"L10nRegistry.cpp",
|
||||
@ -45,9 +48,13 @@ USE_LIBS += ["intlcomponents"]
|
||||
|
||||
if CONFIG["COMPILE_ENVIRONMENT"]:
|
||||
CbindgenHeader("fluent_ffi_generated.h", inputs=["/intl/l10n/rust/fluent-ffi"])
|
||||
CbindgenHeader(
|
||||
"l10nregistry_ffi_generated.h", inputs=["/intl/l10n/rust/l10nregistry-ffi"]
|
||||
)
|
||||
|
||||
EXPORTS.mozilla.intl += [
|
||||
"!fluent_ffi_generated.h",
|
||||
"!l10nregistry_ffi_generated.h",
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell.ini"]
|
||||
|
@ -16,7 +16,6 @@ namespaces = ["mozilla", "intl", "ffi"]
|
||||
[parse]
|
||||
parse_deps = true
|
||||
include = ["fluent", "fluent-bundle", "intl-memoizer"]
|
||||
exclude = ["fxhash"]
|
||||
|
||||
[enum]
|
||||
derive_helper_methods = true
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
pub use fluent::FluentResource;
|
||||
use nsstring::nsACString;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::{
|
||||
mem::{self, ManuallyDrop},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fluent_resource_new(
|
||||
@ -27,12 +29,11 @@ pub extern "C" fn fluent_resource_new(
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fluent_resource_addref(res: &FluentResource) {
|
||||
let raw = Rc::from_raw(res);
|
||||
let raw = ManuallyDrop::new(Rc::from_raw(res));
|
||||
mem::forget(Rc::clone(&raw));
|
||||
mem::forget(raw);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fluent_resource_release(res: &FluentResource) {
|
||||
pub unsafe extern "C" fn fluent_resource_release(res: *const FluentResource) {
|
||||
let _ = Rc::from_raw(res);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
use l10nregistry_ffi::{load_async, load_sync};
|
||||
use l10nregistry_ffi::load::{load_async, load_sync};
|
||||
use moz_task;
|
||||
use std::{
|
||||
sync::atomic::{AtomicBool, Ordering::Relaxed},
|
||||
|
@ -7,5 +7,14 @@ edition = "2018"
|
||||
[dependencies]
|
||||
futures-channel = "0.3"
|
||||
libc = "0.2"
|
||||
cstr = "0.2"
|
||||
log = "0.4"
|
||||
nserror = { path = "../../../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../../../xpcom/rust/nsstring" }
|
||||
l10nregistry = { git = "https://github.com/zbraniecki/l10nregistry-rs", rev = "92d8fbfbbbdffa2047ce01a935a389eb11031f69" }
|
||||
fluent = { version = "0.15.0", features = ["fluent-pseudo"] }
|
||||
unic-langid = "0.9"
|
||||
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" }
|
||||
|
26
intl/l10n/rust/l10nregistry-ffi/cbindgen.toml
Normal file
26
intl/l10n/rust/l10nregistry-ffi/cbindgen.toml
Normal file
@ -0,0 +1,26 @@
|
||||
header = """/* 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/. */"""
|
||||
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
|
||||
#ifndef mozilla_intl_l10n_RegistryBindings_h
|
||||
#error "Don't include this file directly, instead include RegistryBindings.h"
|
||||
#endif
|
||||
"""
|
||||
include_version = true
|
||||
braces = "SameLine"
|
||||
line_length = 100
|
||||
tab_width = 2
|
||||
language = "C++"
|
||||
namespaces = ["mozilla", "intl", "ffi"]
|
||||
includes = ["nsIStreamLoader.h"]
|
||||
|
||||
[parse]
|
||||
parse_deps = true
|
||||
include = ["fluent-bundle", "l10nregistry"]
|
||||
|
||||
[enum]
|
||||
derive_helper_methods = true
|
||||
|
||||
[export.rename]
|
||||
"ThinVec" = "nsTArray"
|
||||
"Promise" = "dom::Promise"
|
88
intl/l10n/rust/l10nregistry-ffi/src/env.rs
Normal file
88
intl/l10n/rust/l10nregistry-ffi/src/env.rs
Normal file
@ -0,0 +1,88 @@
|
||||
/* 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/. */
|
||||
|
||||
use cstr::cstr;
|
||||
use l10nregistry::{env::ErrorReporter, errors::L10nRegistryError};
|
||||
use log::warn;
|
||||
use nserror::{nsresult, NS_ERROR_NOT_AVAILABLE};
|
||||
use nsstring::nsString;
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
fmt::{self, Write},
|
||||
};
|
||||
use xpcom::interfaces;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GeckoEnvironment;
|
||||
|
||||
impl ErrorReporter for GeckoEnvironment {
|
||||
fn report_errors(&self, errors: Vec<L10nRegistryError>) {
|
||||
for error in errors {
|
||||
warn!("L10nRegistry error: {}", error);
|
||||
let result = match error {
|
||||
L10nRegistryError::FluentError { path, loc, error } => log_simple_console_error(
|
||||
&error.to_string(),
|
||||
cstr!("l10n"),
|
||||
false,
|
||||
true,
|
||||
Some(nsString::from(&path)),
|
||||
loc.map_or((0, 0), |(l, c)| (l as u32, c as u32)),
|
||||
interfaces::nsIScriptError::errorFlag as u32,
|
||||
),
|
||||
L10nRegistryError::MissingResource { .. } => log_simple_console_error(
|
||||
&error.to_string(),
|
||||
cstr!("l10n"),
|
||||
false,
|
||||
true,
|
||||
None,
|
||||
(0, 0),
|
||||
interfaces::nsIScriptError::warningFlag as u32,
|
||||
),
|
||||
};
|
||||
if let Err(err) = result {
|
||||
warn!("Error while reporting an error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_simple_console_error(
|
||||
error: &impl fmt::Display,
|
||||
category: &CStr,
|
||||
from_private_window: bool,
|
||||
from_chrome_context: bool,
|
||||
path: Option<nsString>,
|
||||
pos: (u32, u32),
|
||||
error_flags: u32,
|
||||
) -> Result<(), nsresult> {
|
||||
// Format whatever error argument into a wide string with `Display`.
|
||||
let mut error_str = nsString::new();
|
||||
write!(&mut error_str, "{}", error).expect("nsString has an infallible Write impl");
|
||||
|
||||
// Get the relevant services, and create the script error object.
|
||||
let console_service =
|
||||
xpcom::get_service::<interfaces::nsIConsoleService>(cstr!("@mozilla.org/consoleservice;1"))
|
||||
.ok_or(NS_ERROR_NOT_AVAILABLE)?;
|
||||
let script_error =
|
||||
xpcom::create_instance::<interfaces::nsIScriptError>(cstr!("@mozilla.org/scripterror;1"))
|
||||
.ok_or(NS_ERROR_NOT_AVAILABLE)?;
|
||||
unsafe {
|
||||
script_error
|
||||
.Init(
|
||||
&*error_str,
|
||||
&*path.unwrap_or(nsString::new()), /* aSourceName */
|
||||
&*nsString::new(), /* aSourceLine */
|
||||
pos.0, /* aLineNumber */
|
||||
pos.1, /* aColNumber */
|
||||
error_flags,
|
||||
category.as_ptr(),
|
||||
from_private_window,
|
||||
from_chrome_context,
|
||||
)
|
||||
.to_result()?;
|
||||
|
||||
console_service.LogMessage(&**script_error).to_result()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
53
intl/l10n/rust/l10nregistry-ffi/src/fetcher.rs
Normal file
53
intl/l10n/rust/l10nregistry-ffi/src/fetcher.rs
Normal file
@ -0,0 +1,53 @@
|
||||
/* 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 l10nregistry::source::FileFetcher;
|
||||
|
||||
use std::io;
|
||||
|
||||
pub struct GeckoFileFetcher;
|
||||
|
||||
fn try_string_from_box_u8(input: Box<[u8]>) -> io::Result<String> {
|
||||
String::from_utf8(input.into())
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.utf8_error()))
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FileFetcher for GeckoFileFetcher {
|
||||
fn fetch_sync(&self, path: &str) -> io::Result<String> {
|
||||
crate::load::load_sync(path).and_then(try_string_from_box_u8)
|
||||
}
|
||||
|
||||
async fn fetch(&self, path: &str) -> io::Result<String> {
|
||||
crate::load::load_async(path)
|
||||
.await
|
||||
.and_then(try_string_from_box_u8)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockFileFetcher {
|
||||
fs: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl MockFileFetcher {
|
||||
pub fn new(fs: Vec<(String, String)>) -> Self {
|
||||
Self { fs }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FileFetcher for MockFileFetcher {
|
||||
fn fetch_sync(&self, path: &str) -> io::Result<String> {
|
||||
for (p, source) in &self.fs {
|
||||
if p == path {
|
||||
return Ok(source.clone());
|
||||
}
|
||||
}
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "File not found"))
|
||||
}
|
||||
|
||||
async fn fetch(&self, path: &str) -> io::Result<String> {
|
||||
self.fetch_sync(path)
|
||||
}
|
||||
}
|
@ -2,107 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
use futures_channel::oneshot::channel;
|
||||
use libc::c_void;
|
||||
use nserror::nsresult;
|
||||
use nsstring::{nsACString, nsCString, nsCStringLike};
|
||||
use std::io::{self, Error, ErrorKind};
|
||||
|
||||
trait ToIoResult {
|
||||
fn to_io_result(self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl ToIoResult for nsresult {
|
||||
fn to_io_result(self) -> io::Result<()> {
|
||||
if self.failed() {
|
||||
return Err(Error::new(ErrorKind::Other, self));
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod ffi {
|
||||
use super::*;
|
||||
|
||||
pub fn load(
|
||||
path: &nsACString,
|
||||
callback: Option<extern "C" fn(*mut c_void, *mut nsACString, nsresult)>,
|
||||
closure: *mut c_void,
|
||||
) -> io::Result<()> {
|
||||
extern "C" {
|
||||
fn L10nRegistryLoad(
|
||||
aPath: *const nsACString,
|
||||
callback: Option<extern "C" fn(*mut c_void, *mut nsACString, nsresult)>,
|
||||
closure: *mut c_void,
|
||||
) -> nsresult;
|
||||
}
|
||||
unsafe { L10nRegistryLoad(path, callback, closure) }.to_io_result()
|
||||
}
|
||||
|
||||
pub fn load_sync(path: &nsACString, result: &mut nsACString) -> io::Result<()> {
|
||||
extern "C" {
|
||||
fn L10nRegistryLoadSync(aPath: *const nsACString, aRetVal: *mut nsACString) -> nsresult;
|
||||
}
|
||||
unsafe { L10nRegistryLoadSync(path, result) }.to_io_result()
|
||||
}
|
||||
|
||||
/// Swap the memory to take ownership of the string data
|
||||
#[allow(non_snake_case)]
|
||||
pub fn take_nsACString(s: &mut nsACString) -> nsCString {
|
||||
let mut result = nsCString::new();
|
||||
result.take_from(s);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
struct CallbackClosure {
|
||||
cb: Box<dyn FnOnce(io::Result<&mut nsACString>)>,
|
||||
}
|
||||
|
||||
impl CallbackClosure {
|
||||
fn new<CB>(cb: CB) -> *mut Self
|
||||
where
|
||||
CB: FnOnce(io::Result<&mut nsACString>) + 'static,
|
||||
{
|
||||
let boxed = Box::new(Self {
|
||||
cb: Box::new(cb) as Box<dyn FnOnce(io::Result<&mut nsACString>)>,
|
||||
});
|
||||
Box::into_raw(boxed)
|
||||
}
|
||||
|
||||
fn call(self: Box<Self>, s: io::Result<&mut nsACString>) {
|
||||
(self.cb)(s)
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn load_async_cb(closure: *mut c_void, string: *mut nsACString, success: nsresult) {
|
||||
let result = success.to_io_result().map(|_| unsafe { &mut *string });
|
||||
let closure = unsafe {
|
||||
debug_assert_ne!(closure, std::ptr::null_mut());
|
||||
Box::from_raw(closure as *mut CallbackClosure)
|
||||
};
|
||||
closure.call(result);
|
||||
}
|
||||
|
||||
pub async fn load_async(path: impl nsCStringLike) -> io::Result<nsCString> {
|
||||
let (sender, receiver) = channel::<io::Result<nsCString>>();
|
||||
|
||||
let closure = CallbackClosure::new(move |result| {
|
||||
let result = result.map(ffi::take_nsACString);
|
||||
sender.send(result).expect("Failed to send result");
|
||||
});
|
||||
|
||||
ffi::load(&*path.adapt(), Some(load_async_cb), closure as *mut c_void)?;
|
||||
|
||||
let result = receiver
|
||||
.await
|
||||
.map_err(|_| Error::new(ErrorKind::Interrupted, "canceled"))?;
|
||||
result
|
||||
}
|
||||
|
||||
pub fn load_sync(path: impl nsCStringLike) -> io::Result<nsCString> {
|
||||
let mut result = nsCString::new();
|
||||
ffi::load_sync(&*path.adapt(), &mut result)?;
|
||||
Ok(result)
|
||||
}
|
||||
mod env;
|
||||
mod fetcher;
|
||||
pub mod load;
|
||||
mod source;
|
||||
|
115
intl/l10n/rust/l10nregistry-ffi/src/load.rs
Normal file
115
intl/l10n/rust/l10nregistry-ffi/src/load.rs
Normal file
@ -0,0 +1,115 @@
|
||||
/* 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/. */
|
||||
|
||||
use futures_channel::oneshot;
|
||||
use nserror::{nsresult, NS_OK, NS_SUCCESS_ADOPTED_DATA};
|
||||
use nsstring::{nsACString, nsCStringLike};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
ffi::c_void,
|
||||
io::{self, Error, ErrorKind},
|
||||
ptr,
|
||||
};
|
||||
use xpcom::{
|
||||
interfaces::{nsIStreamLoader, nsIStreamLoaderObserver, nsISupports},
|
||||
xpcom,
|
||||
};
|
||||
|
||||
unsafe fn boxed_slice_from_raw(ptr: *mut u8, len: usize) -> Box<[u8]> {
|
||||
if ptr.is_null() {
|
||||
// It is undefined behaviour to create a `Box<[u8]>` with a null pointer,
|
||||
// so avoid that case.
|
||||
assert_eq!(len, 0);
|
||||
Box::new([])
|
||||
} else {
|
||||
Box::from_raw(ptr::slice_from_raw_parts_mut(ptr, len))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(xpcom)]
|
||||
#[xpimplements(nsIStreamLoaderObserver)]
|
||||
#[refcnt = "nonatomic"]
|
||||
struct InitStreamLoaderObserver {
|
||||
sender: Cell<Option<oneshot::Sender<Result<Box<[u8]>, nsresult>>>>,
|
||||
}
|
||||
|
||||
impl StreamLoaderObserver {
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn OnStreamComplete(
|
||||
&self,
|
||||
_loader: *const nsIStreamLoader,
|
||||
_ctxt: *const nsISupports,
|
||||
status: nsresult,
|
||||
result_length: u32,
|
||||
result: *const u8,
|
||||
) -> nsresult {
|
||||
let sender = match self.sender.take() {
|
||||
Some(sender) => sender,
|
||||
None => return NS_OK,
|
||||
};
|
||||
|
||||
if status.failed() {
|
||||
sender.send(Err(status)).expect("Failed to send data");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// safety: take ownership of the data passed in. This is OK because we
|
||||
// have configured Rust and C++ to use the same allocator, and our
|
||||
// caller won't free the `result` pointer if we return
|
||||
// NS_SUCCESS_ADOPTED_DATA.
|
||||
sender
|
||||
.send(Ok(boxed_slice_from_raw(
|
||||
result as *mut u8,
|
||||
result_length as usize,
|
||||
)))
|
||||
.expect("Failed to send data");
|
||||
NS_SUCCESS_ADOPTED_DATA
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn L10nRegistryLoad(
|
||||
path: *const nsACString,
|
||||
observer: *const nsIStreamLoaderObserver,
|
||||
) -> nsresult;
|
||||
|
||||
fn L10nRegistryLoadSync(
|
||||
aPath: *const nsACString,
|
||||
aData: *mut *mut c_void,
|
||||
aSize: *mut u64,
|
||||
) -> nsresult;
|
||||
}
|
||||
|
||||
pub async fn load_async(path: impl nsCStringLike) -> io::Result<Box<[u8]>> {
|
||||
let (sender, receiver) = oneshot::channel::<Result<Box<[u8]>, nsresult>>();
|
||||
let observer = StreamLoaderObserver::allocate(InitStreamLoaderObserver {
|
||||
sender: Cell::new(Some(sender)),
|
||||
});
|
||||
unsafe {
|
||||
L10nRegistryLoad(&*path.adapt(), observer.coerce())
|
||||
.to_result()
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))?;
|
||||
}
|
||||
receiver
|
||||
.await
|
||||
.expect("Failed to receive from observer.")
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
}
|
||||
|
||||
pub fn load_sync(path: impl nsCStringLike) -> io::Result<Box<[u8]>> {
|
||||
let mut data_ptr: *mut c_void = ptr::null_mut();
|
||||
let mut data_length: u64 = 0;
|
||||
unsafe {
|
||||
L10nRegistryLoadSync(&*path.adapt(), &mut data_ptr, &mut data_length)
|
||||
.to_result()
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))?;
|
||||
|
||||
// The call succeeded, meaning `data_ptr` and `size` have been filled in with owning pointers to actual data payloads (or null).
|
||||
// If we get a null, return a successful read of the empty file.
|
||||
Ok(boxed_slice_from_raw(
|
||||
data_ptr as *mut u8,
|
||||
data_length as usize,
|
||||
))
|
||||
}
|
||||
}
|
310
intl/l10n/rust/l10nregistry-ffi/src/source.rs
Normal file
310
intl/l10n/rust/l10nregistry-ffi/src/source.rs
Normal file
@ -0,0 +1,310 @@
|
||||
/* 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 super::fetcher::{GeckoFileFetcher, MockFileFetcher};
|
||||
use crate::env::GeckoEnvironment;
|
||||
|
||||
use fluent::FluentResource;
|
||||
use l10nregistry::source::{FileSource, ResourceStatus};
|
||||
|
||||
use nsstring::{nsACString, nsCString};
|
||||
use thin_vec::ThinVec;
|
||||
use unic_langid::LanguageIdentifier;
|
||||
|
||||
use std::{mem, rc::Rc};
|
||||
use xpcom::RefPtr;
|
||||
|
||||
#[repr(C)]
|
||||
pub enum L10nFileSourceStatus {
|
||||
None,
|
||||
EmptyName,
|
||||
EmptyPrePath,
|
||||
EmptyResId,
|
||||
InvalidLocaleCode,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_new(
|
||||
name: &nsACString,
|
||||
locales: &ThinVec<nsCString>,
|
||||
pre_path: &nsACString,
|
||||
status: &mut L10nFileSourceStatus,
|
||||
) -> *const FileSource {
|
||||
if name.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyName;
|
||||
return std::ptr::null();
|
||||
}
|
||||
if pre_path.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyPrePath;
|
||||
return std::ptr::null();
|
||||
}
|
||||
|
||||
let locales: Result<Vec<LanguageIdentifier>, _> =
|
||||
locales.iter().map(|l| l.to_utf8().parse()).collect();
|
||||
|
||||
let locales = match locales {
|
||||
Ok(locales) => locales,
|
||||
Err(..) => {
|
||||
*status = L10nFileSourceStatus::InvalidLocaleCode;
|
||||
return std::ptr::null();
|
||||
}
|
||||
};
|
||||
|
||||
let mut source = FileSource::new(
|
||||
name.to_string(),
|
||||
locales,
|
||||
pre_path.to_string(),
|
||||
Default::default(),
|
||||
GeckoFileFetcher,
|
||||
);
|
||||
source.set_reporter(GeckoEnvironment);
|
||||
|
||||
*status = L10nFileSourceStatus::None;
|
||||
Rc::into_raw(Rc::new(source))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn l10nfilesource_new_with_index(
|
||||
name: &nsACString,
|
||||
locales: &ThinVec<nsCString>,
|
||||
pre_path: &nsACString,
|
||||
index_elements: *const nsCString,
|
||||
index_length: usize,
|
||||
status: &mut L10nFileSourceStatus,
|
||||
) -> *const FileSource {
|
||||
if name.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyName;
|
||||
return std::ptr::null();
|
||||
}
|
||||
if pre_path.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyPrePath;
|
||||
return std::ptr::null();
|
||||
}
|
||||
|
||||
let locales: Result<Vec<LanguageIdentifier>, _> =
|
||||
locales.iter().map(|l| l.to_utf8().parse()).collect();
|
||||
|
||||
let index = if index_length > 0 {
|
||||
assert!(!index_elements.is_null());
|
||||
std::slice::from_raw_parts(index_elements, index_length)
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
let locales = match locales {
|
||||
Ok(locales) => locales,
|
||||
Err(..) => {
|
||||
*status = L10nFileSourceStatus::InvalidLocaleCode;
|
||||
return std::ptr::null();
|
||||
}
|
||||
};
|
||||
|
||||
let mut source = FileSource::new_with_index(
|
||||
name.to_string(),
|
||||
locales,
|
||||
pre_path.to_string(),
|
||||
Default::default(),
|
||||
GeckoFileFetcher,
|
||||
index,
|
||||
);
|
||||
source.set_reporter(GeckoEnvironment);
|
||||
|
||||
*status = L10nFileSourceStatus::None;
|
||||
Rc::into_raw(Rc::new(source))
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct L10nFileSourceMockFile {
|
||||
path: nsCString,
|
||||
source: nsCString,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_new_mock(
|
||||
name: &nsACString,
|
||||
locales: &ThinVec<nsCString>,
|
||||
pre_path: &nsACString,
|
||||
fs: &ThinVec<L10nFileSourceMockFile>,
|
||||
status: &mut L10nFileSourceStatus,
|
||||
) -> *const FileSource {
|
||||
if name.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyName;
|
||||
return std::ptr::null();
|
||||
}
|
||||
if pre_path.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyPrePath;
|
||||
return std::ptr::null();
|
||||
}
|
||||
|
||||
let locales: Result<Vec<LanguageIdentifier>, _> =
|
||||
locales.iter().map(|l| l.to_utf8().parse()).collect();
|
||||
|
||||
let locales = match locales {
|
||||
Ok(locales) => locales,
|
||||
Err(..) => {
|
||||
*status = L10nFileSourceStatus::InvalidLocaleCode;
|
||||
return std::ptr::null();
|
||||
}
|
||||
};
|
||||
|
||||
let fs = fs
|
||||
.iter()
|
||||
.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);
|
||||
source.set_reporter(GeckoEnvironment);
|
||||
|
||||
*status = L10nFileSourceStatus::None;
|
||||
Rc::into_raw(Rc::new(source))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn l10nfilesource_addref(source: &FileSource) {
|
||||
let raw = Rc::from_raw(source);
|
||||
mem::forget(Rc::clone(&raw));
|
||||
mem::forget(raw);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn l10nfilesource_release(source: *const FileSource) {
|
||||
let _ = Rc::from_raw(source);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_get_name(source: &FileSource, ret_val: &mut nsACString) {
|
||||
ret_val.assign(&source.name);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_get_locales(
|
||||
source: &FileSource,
|
||||
ret_val: &mut ThinVec<nsCString>,
|
||||
) {
|
||||
for locale in source.locales() {
|
||||
ret_val.push(locale.to_string().into());
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_get_prepath(source: &FileSource, ret_val: &mut nsACString) {
|
||||
ret_val.assign(&source.pre_path);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_get_index(
|
||||
source: &FileSource,
|
||||
ret_val: &mut ThinVec<nsCString>,
|
||||
) -> bool {
|
||||
if let Some(index) = source.get_index() {
|
||||
for entry in index {
|
||||
ret_val.push(entry.to_string().into());
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_has_file(
|
||||
source: &FileSource,
|
||||
locale: &nsACString,
|
||||
path: &nsACString,
|
||||
status: &mut L10nFileSourceStatus,
|
||||
present: &mut bool,
|
||||
) -> bool {
|
||||
if path.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyResId;
|
||||
return false;
|
||||
}
|
||||
|
||||
let locale = match locale.to_utf8().parse() {
|
||||
Ok(locale) => locale,
|
||||
Err(..) => {
|
||||
*status = L10nFileSourceStatus::InvalidLocaleCode;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
*status = L10nFileSourceStatus::None;
|
||||
// To work around Option<bool> we return bool for the option,
|
||||
// and the `present` argument is the value of it.
|
||||
if let Some(val) = source.has_file(&locale, &path.to_utf8()) {
|
||||
*present = val;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn l10nfilesource_fetch_file_sync(
|
||||
source: &FileSource,
|
||||
locale: &nsACString,
|
||||
path: &nsACString,
|
||||
status: &mut L10nFileSourceStatus,
|
||||
) -> *const FluentResource {
|
||||
if path.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyResId;
|
||||
return std::ptr::null();
|
||||
}
|
||||
|
||||
let locale = match locale.to_utf8().parse() {
|
||||
Ok(locale) => locale,
|
||||
Err(..) => {
|
||||
*status = L10nFileSourceStatus::InvalidLocaleCode;
|
||||
return std::ptr::null();
|
||||
}
|
||||
};
|
||||
|
||||
*status = L10nFileSourceStatus::None;
|
||||
if let Some(res) = source.fetch_file_sync(&locale, &path.to_utf8(), false) {
|
||||
Rc::into_raw(res)
|
||||
} else {
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn l10nfilesource_fetch_file(
|
||||
source: &FileSource,
|
||||
locale: &nsACString,
|
||||
path: &nsACString,
|
||||
promise: &xpcom::Promise,
|
||||
callback: extern "C" fn(&xpcom::Promise, Option<&FluentResource>),
|
||||
status: &mut L10nFileSourceStatus,
|
||||
) {
|
||||
if path.is_empty() {
|
||||
*status = L10nFileSourceStatus::EmptyResId;
|
||||
return;
|
||||
}
|
||||
|
||||
let locale = match locale.to_utf8().parse() {
|
||||
Ok(locale) => locale,
|
||||
Err(..) => {
|
||||
*status = L10nFileSourceStatus::InvalidLocaleCode;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
*status = L10nFileSourceStatus::None;
|
||||
|
||||
let path = path.to_utf8();
|
||||
|
||||
match source.fetch_file(&locale, &path) {
|
||||
ResourceStatus::Missing => callback(promise, None),
|
||||
ResourceStatus::Loaded(res) => callback(promise, Some(&res)),
|
||||
res @ ResourceStatus::Loading(_) => {
|
||||
let strong_promise = RefPtr::new(promise);
|
||||
moz_task::spawn_current_thread(async move {
|
||||
callback(&strong_promise, res.await.as_ref().map(|r| &**r));
|
||||
})
|
||||
.expect("Failed to spawn future");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user