Bug 1660392 - [l10nregistry] part3: Enable L10nRegistry IPC. r=nika,emilio,platform-i18n-reviewers,dminor

Depends on D102096

Differential Revision: https://phabricator.services.mozilla.com/D105572
This commit is contained in:
Zibi Braniecki 2021-07-30 16:47:47 +00:00
parent f42090a25a
commit d3b0c08941
10 changed files with 361 additions and 26 deletions

View File

@ -91,6 +91,7 @@
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/hal_sandbox/PHalChild.h"
#include "mozilla/intl/L10nRegistry.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/Endpoint.h"
@ -1330,11 +1331,14 @@ void ContentChild::InitXPCOM(
RecvSetOffline(aXPCOMInit.isOffline());
RecvSetConnectivity(aXPCOMInit.isConnected());
// XXX(Bug 1633675) The LocaleService calls could also move the arguments.
LocaleService::GetInstance()->AssignAppLocales(aXPCOMInit.appLocales());
LocaleService::GetInstance()->AssignRequestedLocales(
aXPCOMInit.requestedLocales());
L10nRegistry::RegisterFileSourcesFromParentProcess(
aXPCOMInit.l10nFileSources());
RecvSetCaptivePortalState(aXPCOMInit.captivePortalState());
RecvBidiKeyboardNotify(aXPCOMInit.isLangRTL(),
aXPCOMInit.haveBidiKeyboards());
@ -2355,6 +2359,12 @@ mozilla::ipc::IPCResult ContentChild::RecvRegisterStringBundles(
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvUpdateL10nFileSources(
nsTArray<mozilla::dom::L10nFileSourceDescriptor>&& aDescriptors) {
L10nRegistry::RegisterFileSourcesFromParentProcess(aDescriptors);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvUpdateSharedData(
const FileDescriptor& aMapFile, const uint32_t& aMapSize,
nsTArray<IPCBlob>&& aBlobs, nsTArray<nsCString>&& aChangedKeys) {

View File

@ -331,6 +331,9 @@ class ContentChild final : public PContentChild,
mozilla::ipc::IPCResult RecvRegisterStringBundles(
nsTArray<StringBundleDescriptor>&& stringBundles);
mozilla::ipc::IPCResult RecvUpdateL10nFileSources(
nsTArray<L10nFileSourceDescriptor>&& aDescriptors);
mozilla::ipc::IPCResult RecvUpdateSharedData(
const FileDescriptor& aMapFile, const uint32_t& aMapSize,
nsTArray<IPCBlob>&& aBlobs, nsTArray<nsCString>&& aChangedKeys);

View File

@ -135,6 +135,7 @@
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/hal_sandbox/PHalParent.h"
#include "mozilla/intl/L10nRegistry.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
@ -2875,6 +2876,9 @@ bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
LocaleService::GetInstance()->GetRequestedLocales(
xpcomInit.requestedLocales());
L10nRegistry::GetParentProcessFileSourceDescriptors(
xpcomInit.l10nFileSources());
nsCOMPtr<nsIClipboard> clipboard(
do_GetService("@mozilla.org/widget/clipboard;1"));
MOZ_ASSERT(clipboard, "No clipboard?");

View File

@ -325,6 +325,13 @@ struct GMPCapabilityData
GMPAPITags[] capabilities;
};
struct L10nFileSourceDescriptor {
nsCString name;
nsCString[] locales;
nsCString prePath;
nsCString[] index;
};
struct XPCOMInitData
{
bool isOffline;
@ -341,6 +348,7 @@ struct XPCOMInitData
GfxInfoFeatureStatus[] gfxFeatureStatus;
nsCString[] appLocales;
nsCString[] requestedLocales;
L10nFileSourceDescriptor[] l10nFileSources;
DynamicScalarDefinition[] dynamicScalarDefs;
SystemParameterKVPair[] systemParameters;
};
@ -650,6 +658,8 @@ child:
async UpdateAppLocales(nsCString[] appLocales);
async UpdateRequestedLocales(nsCString[] requestedLocales);
async UpdateL10nFileSources(L10nFileSourceDescriptor[] sources);
async RegisterStringBundles(StringBundleDescriptor[] stringBundles);
async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize,

View File

@ -11,9 +11,14 @@
#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 {
@ -227,6 +232,44 @@ already_AddRefed<FluentBundleAsyncIterator> L10nRegistry::GenerateBundles(
return do_AddRef(new FluentBundleAsyncIterator(mGlobal, std::move(iter)));
}
/* 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->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->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) {
@ -324,6 +367,18 @@ nsresult L10nRegistryLoadSync(const nsACString* aPath, void** aData,
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

View File

@ -12,10 +12,15 @@
#include "nsCycleCollectionParticipant.h"
#include "mozilla/dom/L10nRegistryBinding.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/intl/RegistryBindings.h"
#include "mozilla/intl/FluentBindings.h"
class nsIGlobalObject;
namespace mozilla::dom {
class L10nFileSourceDescriptor;
}
namespace mozilla::intl {
class FluentBundleAsyncIterator final : public nsWrapperCache {
@ -78,6 +83,11 @@ class L10nRegistry final : public nsWrapperCache {
static already_AddRefed<L10nRegistry> GetInstance(
const dom::GlobalObject& aGlobal);
static void GetParentProcessFileSourceDescriptors(
nsTArray<dom::L10nFileSourceDescriptor>& aRetVal);
static void RegisterFileSourcesFromParentProcess(
const nsTArray<dom::L10nFileSourceDescriptor>& aDescriptors);
static nsresult Load(const nsACString& aPath,
nsIStreamLoaderObserver* aObserver);
static nsresult LoadSync(const nsACString& aPath, void** aData,

View File

@ -41,9 +41,7 @@ XPIDL_SOURCES += [
XPIDL_MODULE = "locale"
LOCAL_INCLUDES += [
"/dom/base",
]
include("/ipc/chromium/chromium-config.mozbuild")
USE_LIBS += ["intlcomponents"]

View File

@ -7,3 +7,4 @@ mod fetcher;
pub mod load;
mod registry;
mod source;
mod xpcom_utils;

View File

@ -8,7 +8,7 @@ use std::mem;
use std::rc::Rc;
use thin_vec::ThinVec;
use crate::{env::GeckoEnvironment, fetcher::GeckoFileFetcher};
use crate::{env::GeckoEnvironment, fetcher::GeckoFileFetcher, xpcom_utils::is_parent_process};
use fluent_fallback::generator::BundleGenerator;
use futures_channel::mpsc::{unbounded, UnboundedSender};
pub use l10nregistry::{
@ -40,30 +40,28 @@ impl BundleAdapter for GeckoBundleAdapter {
}
thread_local!(static L10N_REGISTRY: Rc<GeckoL10nRegistry> = {
let env = GeckoEnvironment;
let mut reg = L10nRegistry::with_provider(env);
let sources = if is_parent_process() {
let packaged_locales = get_packaged_locales();
let entries = get_l10n_registry_category_entries();
let packaged_locales = vec!["en-US".parse().unwrap()];
Some(entries
.into_iter()
.map(|entry| {
FileSource::new(
entry.entry.to_string(),
packaged_locales.clone(),
entry.value.to_string(),
Default::default(),
GeckoFileFetcher,
)
})
.collect())
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();
} else {
None
};
let _ = reg.register_sources(vec![toolkit_fs, browser_fs]).report_error();
Rc::new(reg)
create_l10n_registry(sources)
});
pub type GeckoL10nRegistry = L10nRegistry<GeckoEnvironment, GeckoBundleAdapter>;
@ -82,6 +80,79 @@ impl<V> GeckoReportError<V, L10nRegistrySetupError> for Result<V, L10nRegistrySe
}
}
#[derive(Debug)]
#[repr(C)]
pub struct L10nFileSourceDescriptor {
name: nsCString,
locales: ThinVec<nsCString>,
pre_path: nsCString,
index: ThinVec<nsCString>,
}
fn get_l10n_registry_category_entries() -> Vec<crate::xpcom_utils::CategoryEntry> {
crate::xpcom_utils::get_category_entries(&nsCString::from("l10n-registry")).unwrap_or_default()
}
fn get_packaged_locales() -> Vec<LanguageIdentifier> {
crate::xpcom_utils::get_packaged_locales()
.map(|locales| {
locales
.into_iter()
.map(|s| s.to_utf8().parse().expect("Failed to parse locale."))
.collect()
})
.unwrap_or_default()
}
fn create_l10n_registry(sources: Option<Vec<FileSource>>) -> Rc<GeckoL10nRegistry> {
let env = GeckoEnvironment;
let mut reg = L10nRegistry::with_provider(env);
reg.set_adapt_bundle(GeckoBundleAdapter::default())
.expect("Failed to set bundle adaptation closure.");
if let Some(sources) = sources {
reg.register_sources(sources)
.expect("Failed to register sources.");
}
Rc::new(reg)
}
pub fn set_l10n_registry(new_sources: &ThinVec<L10nFileSourceDescriptor>) {
L10N_REGISTRY.with(|reg| {
let new_source_names: Vec<_> = new_sources
.iter()
.map(|d| d.name.to_utf8().to_string())
.collect();
let old_sources = reg.get_source_names().unwrap();
let mut sources_to_be_removed = vec![];
for name in &old_sources {
if !new_source_names.contains(&name) {
sources_to_be_removed.push(name);
}
}
reg.remove_sources(sources_to_be_removed).unwrap();
let mut add_sources = vec![];
for desc in new_sources {
if !old_sources.contains(&desc.name.to_string()) {
add_sources.push(FileSource::new(
desc.name.to_string(),
desc.locales
.iter()
.map(|s| s.to_utf8().parse().unwrap())
.collect(),
desc.pre_path.to_string(),
Default::default(),
GeckoFileFetcher,
));
}
}
reg.register_sources(add_sources).unwrap();
});
}
pub fn get_l10n_registry() -> Rc<GeckoL10nRegistry> {
L10N_REGISTRY.with(|reg| reg.clone())
}
@ -109,6 +180,51 @@ pub extern "C" fn l10nregistry_instance_get() -> *const GeckoL10nRegistry {
Rc::into_raw(reg)
}
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_get_parent_process_sources(
sources: &mut ThinVec<L10nFileSourceDescriptor>,
) {
debug_assert!(
is_parent_process(),
"This should be called only in parent process."
);
// If at the point when the first content process is being initialized, the parent
// process `L10nRegistryService` has not been initialized yet, this will trigger it.
//
// This is architecturally imperfect, but acceptable for simplicity reasons because
// `L10nRegistry` instance is cheap and mainly servers as a store of state.
let reg = get_l10n_registry();
for name in reg.get_source_names().unwrap() {
let source = reg.get_source(&name).unwrap().unwrap();
let descriptor = L10nFileSourceDescriptor {
name: source.name.as_str().into(),
locales: source
.locales()
.iter()
.map(|l| l.to_string().into())
.collect(),
pre_path: source.pre_path.as_str().into(),
index: source
.get_index()
.map(|index| index.into_iter().map(|s| s.into()).collect())
.unwrap_or_default(),
};
sources.push(descriptor);
}
}
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_register_parent_process_sources(
sources: &ThinVec<L10nFileSourceDescriptor>,
) {
debug_assert!(
!is_parent_process(),
"This should be called only in content process."
);
set_l10n_registry(sources);
}
#[no_mangle]
pub unsafe extern "C" fn l10nregistry_addref(reg: &GeckoL10nRegistry) {
let raw = Rc::from_raw(reg);
@ -131,6 +247,20 @@ pub extern "C" fn l10nregistry_get_available_locales(
}
}
fn broadcast_settings_if_parent(reg: &GeckoL10nRegistry) {
if !is_parent_process() {
return;
}
L10N_REGISTRY.with(|reg_service| {
if std::ptr::eq(Rc::as_ptr(reg_service), reg) {
unsafe {
L10nRegistrySendUpdateL10nFileSources();
}
}
});
}
#[no_mangle]
pub extern "C" fn l10nregistry_register_sources(
reg: &GeckoL10nRegistry,
@ -139,6 +269,8 @@ pub extern "C" fn l10nregistry_register_sources(
let _ = reg
.register_sources(sources.iter().map(|&s| s.clone()).collect())
.report_error();
broadcast_settings_if_parent(reg);
}
#[no_mangle]
@ -149,6 +281,7 @@ pub extern "C" fn l10nregistry_update_sources(
let _ = reg
.update_sources(sources.iter().map(|&s| s.clone()).collect())
.report_error();
broadcast_settings_if_parent(reg);
}
#[no_mangle]
@ -163,6 +296,7 @@ pub unsafe extern "C" fn l10nregistry_remove_sources(
let sources = std::slice::from_raw_parts(sources_elements, sources_length);
let _ = reg.remove_sources(sources.to_vec()).report_error();
broadcast_settings_if_parent(reg);
}
#[no_mangle]
@ -204,6 +338,8 @@ pub extern "C" fn l10nregistry_get_source(
#[no_mangle]
pub extern "C" fn l10nregistry_clear_sources(reg: &GeckoL10nRegistry) {
let _ = reg.clear_sources().report_error();
broadcast_settings_if_parent(reg);
}
#[no_mangle]
@ -337,3 +473,7 @@ pub extern "C" fn fluent_bundle_async_iterator_next(
callback(promise, std::ptr::null_mut());
}
}
extern "C" {
pub fn L10nRegistrySendUpdateL10nFileSources();
}

View File

@ -0,0 +1,104 @@
/* 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 nsstring::{nsACString, nsCString};
use std::marker::PhantomData;
use thin_vec::ThinVec;
use xpcom::{
get_service, getter_addrefs,
interfaces::{
mozILocaleService, nsICategoryEntry, nsICategoryManager, nsISimpleEnumerator, nsIXULRuntime,
},
RefPtr, XpCom,
};
pub struct IterSimpleEnumerator<T> {
enumerator: RefPtr<nsISimpleEnumerator>,
phantom: PhantomData<T>,
}
impl<T: XpCom> IterSimpleEnumerator<T> {
/// Convert a `nsISimpleEnumerator` into a rust `Iterator` type.
pub fn new(enumerator: RefPtr<nsISimpleEnumerator>) -> Self {
IterSimpleEnumerator {
enumerator,
phantom: PhantomData,
}
}
}
impl<T: XpCom + 'static> Iterator for IterSimpleEnumerator<T> {
type Item = RefPtr<T>;
fn next(&mut self) -> Option<Self::Item> {
let mut more = false;
unsafe {
self.enumerator
.HasMoreElements(&mut more)
.to_result()
.ok()?
}
if !more {
return None;
}
let element = getter_addrefs(|p| unsafe { self.enumerator.GetNext(p) }).ok()?;
element.query_interface::<T>()
}
}
fn process_type() -> u32 {
if let Some(appinfo) = xpcom::services::get_XULRuntime() {
let mut process_type = nsIXULRuntime::PROCESS_TYPE_DEFAULT as u32;
if unsafe { appinfo.GetProcessType(&mut process_type).succeeded() } {
return process_type;
}
}
nsIXULRuntime::PROCESS_TYPE_DEFAULT as u32
}
pub fn is_parent_process() -> bool {
process_type() == nsIXULRuntime::PROCESS_TYPE_DEFAULT as u32
}
pub fn get_packaged_locales() -> Option<ThinVec<nsCString>> {
let locale_service =
get_service::<mozILocaleService>(cstr!("@mozilla.org/intl/localeservice;1"))?;
let mut locales = ThinVec::new();
unsafe {
locale_service
.GetPackagedLocales(&mut locales)
.to_result()
.ok()?;
}
Some(locales)
}
pub struct CategoryEntry {
pub entry: nsCString,
pub value: nsCString,
}
pub fn get_category_entries(category: &nsACString) -> Option<Vec<CategoryEntry>> {
let category_manager =
get_service::<nsICategoryManager>(cstr!("@mozilla.org/categorymanager;1"))?;
let enumerator =
getter_addrefs(|p| unsafe { category_manager.EnumerateCategory(category, p) }).ok()?;
Some(
IterSimpleEnumerator::<nsICategoryEntry>::new(enumerator)
.map(|ientry| {
let mut entry = nsCString::new();
let mut value = nsCString::new();
unsafe {
let _ = ientry.GetEntry(&mut *entry);
let _ = ientry.GetValue(&mut *value);
}
CategoryEntry { entry, value }
})
.collect(),
)
}