diff options
Diffstat (limited to 'intl/l10n/rust/l10nregistry-ffi/src/registry.rs')
-rw-r--r-- | intl/l10n/rust/l10nregistry-ffi/src/registry.rs | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/intl/l10n/rust/l10nregistry-ffi/src/registry.rs b/intl/l10n/rust/l10nregistry-ffi/src/registry.rs new file mode 100644 index 0000000000..846f12273c --- /dev/null +++ b/intl/l10n/rust/l10nregistry-ffi/src/registry.rs @@ -0,0 +1,519 @@ +/* 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, xpcom_utils::is_parent_process}; +use fluent_fallback::{generator::BundleGenerator, types::ResourceType}; +use futures_channel::mpsc::{unbounded, UnboundedSender}; +pub use l10nregistry::{ + errors::L10nRegistrySetupError, + registry::{BundleAdapter, GenerateBundles, GenerateBundlesSync, L10nRegistry}, + source::{FileSource, ResourceId, ToResourceId}, +}; +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 sources = if is_parent_process() { + let packaged_locales = get_packaged_locales(); + let entries = get_l10n_registry_category_entries(); + + Some(entries + .into_iter() + .map(|entry| { + FileSource::new( + entry.entry.to_string(), + Some("app".to_string()), + packaged_locales.clone(), + entry.value.to_string(), + Default::default(), + GeckoFileFetcher, + ) + }) + .collect()) + + } else { + None + }; + + create_l10n_registry(sources) +}); + +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 + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct L10nFileSourceDescriptor { + name: nsCString, + metasource: 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::new(None); + let mut reg = L10nRegistry::with_provider(env); + + reg.set_bundle_adapter(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(), + Some(desc.metasource.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()) +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub enum GeckoResourceType { + Optional, + Required, +} + +#[repr(C)] +pub struct GeckoResourceId { + value: nsCString, + resource_type: GeckoResourceType, +} + +impl From<&GeckoResourceId> for ResourceId { + fn from(resource_id: &GeckoResourceId) -> Self { + resource_id + .value + .to_string() + .to_resource_id(match resource_id.resource_type { + GeckoResourceType::Optional => ResourceType::Optional, + GeckoResourceType::Required => ResourceType::Required, + }) + } +} + +#[repr(C)] +pub enum L10nRegistryStatus { + None, + EmptyName, + InvalidLocaleCode, +} + +#[no_mangle] +pub extern "C" fn l10nregistry_new(use_isolating: bool) -> *const GeckoL10nRegistry { + let env = GeckoEnvironment::new(None); + let mut reg = L10nRegistry::with_provider(env); + let _ = reg + .set_bundle_adapter(GeckoBundleAdapter { use_isolating }) + .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_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.file_source_by_name(&name).unwrap().unwrap(); + let descriptor = L10nFileSourceDescriptor { + name: source.name.as_str().into(), + metasource: source.metasource.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: *const 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: *const 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())); + } +} + +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) { + let locales = reg + .get_available_locales() + .unwrap() + .iter() + .map(|loc| loc.to_string().into()) + .collect(); + + unsafe { + crate::xpcom_utils::set_available_locales(&locales); + L10nRegistrySendUpdateL10nFileSources(); + } + } + }); +} + +#[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(); + + broadcast_settings_if_parent(reg); +} + +#[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(); + broadcast_settings_if_parent(reg); +} + +#[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(); + broadcast_settings_if_parent(reg); +} + +#[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.file_source_by_name(&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(); + + broadcast_settings_if_parent(reg); +} + +#[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 GeckoResourceId, + 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) + .into_iter() + .map(ResourceId::from) + .collect(); + let locales: Result<Vec<LanguageIdentifier>, _> = + locales.into_iter().map(|s| s.to_utf8().parse()).collect(); + + match locales { + Ok(locales) => { + *status = L10nRegistryStatus::None; + 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 GeckoResourceId, + 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) + .into_iter() + .map(ResourceId::from) + .collect(); + let locales: Result<Vec<LanguageIdentifier>, _> = + locales.into_iter().map(|s| s.to_utf8().parse()).collect(); + + match locales { + Ok(locales) => { + *status = L10nRegistryStatus::None; + 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_local("l10nregistry_generate_bundles", 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); + } + }) + .detach(); + 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()); + } +} + +extern "C" { + pub fn L10nRegistrySendUpdateL10nFileSources(); +} |