mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 14:52:16 +00:00
5a5e1c57ed
- Updates Gecko's L10nRegistry class to use the new ResourceId type, which can be either optional or required regarding a particular resource. - Adds JS tests verifying the new behavior. Differential Revision: https://phabricator.services.mozilla.com/D133578
452 lines
15 KiB
C++
452 lines
15 KiB
C++
/* 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 "L10nRegistry.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/URLPreloader.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsILoadInfo.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsString.h"
|
|
#include "nsContentUtils.h"
|
|
#include "FluentResource.h"
|
|
#include "nsICategoryManager.h"
|
|
#include "mozilla/SimpleEnumerator.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/PContent.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/Preferences.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
namespace mozilla::intl {
|
|
|
|
/* FluentBundleIterator */
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
FluentBundleIteratorResult res;
|
|
|
|
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);
|
|
});
|
|
|
|
return promise.forget();
|
|
}
|
|
|
|
already_AddRefed<FluentBundleAsyncIterator>
|
|
FluentBundleAsyncIterator::Values() {
|
|
return do_AddRef(this);
|
|
}
|
|
|
|
/* L10nRegistry */
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(L10nRegistry, mGlobal)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(L10nRegistry, AddRef)
|
|
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(L10nRegistry, Release)
|
|
|
|
L10nRegistry::L10nRegistry(nsIGlobalObject* aGlobal, bool aUseIsolating)
|
|
: mGlobal(aGlobal),
|
|
mRaw(dont_AddRef(ffi::l10nregistry_new(aUseIsolating))) {}
|
|
|
|
L10nRegistry::L10nRegistry(nsIGlobalObject* aGlobal,
|
|
RefPtr<const ffi::GeckoL10nRegistry> aRaw)
|
|
: mGlobal(aGlobal), mRaw(std::move(aRaw)) {}
|
|
|
|
/* static */
|
|
already_AddRefed<L10nRegistry> L10nRegistry::Constructor(
|
|
const GlobalObject& aGlobal, const L10nRegistryOptions& aOptions) {
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
|
return MakeAndAddRef<L10nRegistry>(global,
|
|
aOptions.mBundleOptions.mUseIsolating);
|
|
}
|
|
|
|
/* 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());
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
/* static */
|
|
ffi::GeckoResourceId L10nRegistry::ResourceIdToFFI(
|
|
const nsCString& aResourceId) {
|
|
return ffi::GeckoResourceId{
|
|
aResourceId,
|
|
ffi::GeckoResourceType::Required,
|
|
};
|
|
}
|
|
|
|
/* static */
|
|
ffi::GeckoResourceId L10nRegistry::ResourceIdToFFI(
|
|
const dom::OwningUTF8StringOrResourceId& aResourceId) {
|
|
if (aResourceId.IsUTF8String()) {
|
|
return ffi::GeckoResourceId{
|
|
aResourceId.GetAsUTF8String(),
|
|
ffi::GeckoResourceType::Required,
|
|
};
|
|
}
|
|
return ffi::GeckoResourceId{
|
|
aResourceId.GetAsResourceId().mPath,
|
|
aResourceId.GetAsResourceId().mOptional
|
|
? ffi::GeckoResourceType::Optional
|
|
: ffi::GeckoResourceType::Required,
|
|
};
|
|
}
|
|
|
|
/* static */
|
|
nsTArray<ffi::GeckoResourceId> L10nRegistry::ResourceIdsToFFI(
|
|
const nsTArray<nsCString>& aResourceIds) {
|
|
nsTArray<ffi::GeckoResourceId> ffiResourceIds;
|
|
for (const auto& resourceId : aResourceIds) {
|
|
ffiResourceIds.EmplaceBack(ResourceIdToFFI(resourceId));
|
|
}
|
|
return ffiResourceIds;
|
|
}
|
|
|
|
/* static */
|
|
nsTArray<ffi::GeckoResourceId> L10nRegistry::ResourceIdsToFFI(
|
|
const nsTArray<dom::OwningUTF8StringOrResourceId>& aResourceIds) {
|
|
nsTArray<ffi::GeckoResourceId> ffiResourceIds;
|
|
for (const auto& resourceId : aResourceIds) {
|
|
ffiResourceIds.EmplaceBack(ResourceIdToFFI(resourceId));
|
|
}
|
|
return ffiResourceIds;
|
|
}
|
|
|
|
already_AddRefed<FluentBundleIterator> L10nRegistry::GenerateBundlesSync(
|
|
const nsTArray<nsCString>& aLocales,
|
|
const nsTArray<ffi::GeckoResourceId>& 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<FluentBundleIterator> L10nRegistry::GenerateBundlesSync(
|
|
const dom::Sequence<nsCString>& aLocales,
|
|
const dom::Sequence<dom::OwningUTF8StringOrResourceId>& aResourceIds,
|
|
ErrorResult& aRv) {
|
|
auto ffiResourceIds{ResourceIdsToFFI(aResourceIds)};
|
|
return GenerateBundlesSync(aLocales, ffiResourceIds, aRv);
|
|
}
|
|
|
|
already_AddRefed<FluentBundleAsyncIterator> L10nRegistry::GenerateBundles(
|
|
const nsTArray<nsCString>& aLocales,
|
|
const nsTArray<ffi::GeckoResourceId>& 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)));
|
|
}
|
|
|
|
already_AddRefed<FluentBundleAsyncIterator> L10nRegistry::GenerateBundles(
|
|
const dom::Sequence<nsCString>& aLocales,
|
|
const dom::Sequence<dom::OwningUTF8StringOrResourceId>& aResourceIds,
|
|
ErrorResult& aRv) {
|
|
nsTArray<ffi::GeckoResourceId> resourceIds;
|
|
for (const auto& resourceId : aResourceIds) {
|
|
resourceIds.EmplaceBack(ResourceIdToFFI(resourceId));
|
|
}
|
|
return GenerateBundles(aLocales, resourceIds, aRv);
|
|
}
|
|
|
|
/* static */
|
|
void L10nRegistry::GetParentProcessFileSourceDescriptors(
|
|
nsTArray<L10nFileSourceDescriptor>& aRetVal) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
nsTArray<ffi::L10nFileSourceDescriptor> sources;
|
|
ffi::l10nregistry_get_parent_process_sources(&sources);
|
|
for (const auto& source : sources) {
|
|
auto descriptor = aRetVal.AppendElement();
|
|
descriptor->name() = source.name;
|
|
descriptor->metasource() = source.metasource;
|
|
descriptor->locales().AppendElements(std::move(source.locales));
|
|
descriptor->prePath() = source.pre_path;
|
|
descriptor->index().AppendElements(std::move(source.index));
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void L10nRegistry::RegisterFileSourcesFromParentProcess(
|
|
const nsTArray<L10nFileSourceDescriptor>& aDescriptors) {
|
|
// This means that in content processes the L10nRegistry
|
|
// service instance is created eagerly, not lazily.
|
|
// It is necessary so that the instance can store the sources
|
|
// provided in the IPC init, which, in turn, is necessary
|
|
// for the service to be avialable for sync bundle generation.
|
|
//
|
|
// L10nRegistry is lightweight and performs no operations, so
|
|
// we believe this behavior to be acceptable.
|
|
MOZ_ASSERT(XRE_IsContentProcess());
|
|
nsTArray<ffi::L10nFileSourceDescriptor> sources;
|
|
for (const auto& desc : aDescriptors) {
|
|
auto source = sources.AppendElement();
|
|
source->name = desc.name();
|
|
source->metasource = desc.metasource();
|
|
source->locales.AppendElements(desc.locales());
|
|
source->pre_path = desc.prePath();
|
|
source->index.AppendElements(desc.index());
|
|
}
|
|
ffi::l10nregistry_register_parent_process_sources(&sources);
|
|
}
|
|
|
|
/* 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,
|
|
const nsIStreamLoaderObserver* aObserver) {
|
|
if (!aPath || !aObserver) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return mozilla::intl::L10nRegistry::Load(
|
|
*aPath, const_cast<nsIStreamLoaderObserver*>(aObserver));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void L10nRegistrySendUpdateL10nFileSources() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
nsTArray<L10nFileSourceDescriptor> sources;
|
|
L10nRegistry::GetParentProcessFileSourceDescriptors(sources);
|
|
|
|
nsTArray<ContentParent*> parents;
|
|
ContentParent::GetAll(parents);
|
|
for (ContentParent* parent : parents) {
|
|
Unused << parent->SendUpdateL10nFileSources(sources);
|
|
}
|
|
}
|
|
|
|
} // extern "C"
|
|
|
|
} // namespace mozilla::intl
|