summaryrefslogtreecommitdiffstats
path: root/intl/l10n/rust/l10nregistry-ffi/src/registry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'intl/l10n/rust/l10nregistry-ffi/src/registry.rs')
-rw-r--r--intl/l10n/rust/l10nregistry-ffi/src/registry.rs519
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();
+}