summaryrefslogtreecommitdiffstats
path: root/intl/l10n/rust/l10nregistry-ffi/src/source.rs
diff options
context:
space:
mode:
Diffstat (limited to 'intl/l10n/rust/l10nregistry-ffi/src/source.rs')
-rw-r--r--intl/l10n/rust/l10nregistry-ffi/src/source.rs359
1 files changed, 359 insertions, 0 deletions
diff --git a/intl/l10n/rust/l10nregistry-ffi/src/source.rs b/intl/l10n/rust/l10nregistry-ffi/src/source.rs
new file mode 100644
index 0000000000..7003d8da97
--- /dev/null
+++ b/intl/l10n/rust/l10nregistry-ffi/src/source.rs
@@ -0,0 +1,359 @@
+/* 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, FileSourceOptions, ResourceOption, ResourceStatus, RcResource};
+
+use nsstring::{nsACString, nsCString};
+use thin_vec::ThinVec;
+use unic_langid::LanguageIdentifier;
+
+use std::{borrow::Cow, mem, rc::Rc};
+use xpcom::RefPtr;
+
+#[repr(C)]
+pub enum L10nFileSourceStatus {
+ None,
+ EmptyName,
+ EmptyPrePath,
+ EmptyResId,
+ InvalidLocaleCode,
+}
+
+// For historical reasons we maintain a locale in Firefox with a codename `ja-JP-mac`.
+// This string is an invalid BCP-47 language tag, so we don't store it in Gecko, which uses
+// valid BCP-47 tags only, but rather keep that quirk local to Gecko L10nRegistry file fetcher.
+//
+// Here, if we encounter `ja-JP-mac` (invalid BCP-47), we swap it for a valid equivalent: `ja-JP-macos`.
+//
+// See bug 1726586 for details and fetcher::get_locale_for_gecko.
+fn get_locale_from_gecko<'s>(input: Cow<'s, str>) -> Cow<'s, str> {
+ if input == "ja-JP-mac" {
+ "ja-JP-macos".into()
+ } else {
+ input
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn l10nfilesource_new(
+ name: &nsACString,
+ metasource: &nsACString,
+ locales: &ThinVec<nsCString>,
+ pre_path: &nsACString,
+ allow_override: bool,
+ 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| get_locale_from_gecko(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(),
+ Some(metasource.to_string()),
+ locales,
+ pre_path.to_string(),
+ FileSourceOptions { allow_override },
+ GeckoFileFetcher,
+ );
+ source.set_reporter(GeckoEnvironment::new(None));
+
+ *status = L10nFileSourceStatus::None;
+ Rc::into_raw(Rc::new(source))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn l10nfilesource_new_with_index(
+ name: &nsACString,
+ metasource: &nsACString,
+ locales: &ThinVec<nsCString>,
+ pre_path: &nsACString,
+ index_elements: *const nsCString,
+ index_length: usize,
+ allow_override: bool,
+ 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| get_locale_from_gecko(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(),
+ Some(metasource.to_string()),
+ locales,
+ pre_path.to_string(),
+ FileSourceOptions { allow_override },
+ GeckoFileFetcher,
+ index,
+ );
+ source.set_reporter(GeckoEnvironment::new(None));
+
+ *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,
+ metasource: &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| get_locale_from_gecko(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(),
+ Some(metasource.to_string()),
+ locales,
+ pre_path.to_string(),
+ Default::default(),
+ fetcher,
+ );
+ source.set_reporter(GeckoEnvironment::new(None));
+
+ *status = L10nFileSourceStatus::None;
+ Rc::into_raw(Rc::new(source))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn l10nfilesource_addref(source: *const 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_metasource(source: &FileSource, ret_val: &mut nsACString) {
+ ret_val.assign(&source.metasource);
+}
+
+#[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().into()) {
+ *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;
+ //XXX: Bug 1723191 - if we encounter a request for sync load while async load is in progress
+ // we will discard the async load and force the sync load instead.
+ // There may be a better option but we haven't had time to explore it.
+ if let ResourceOption::Some(res) =
+ source.fetch_file_sync(&locale, &path.to_utf8().into(), /* overload */ true)
+ {
+ 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().into();
+
+ match source.fetch_file(&locale, &path) {
+ ResourceStatus::MissingOptional => callback(promise, None),
+ ResourceStatus::MissingRequired => callback(promise, None),
+ ResourceStatus::Loaded(res) => callback(promise, Some(&res)),
+ res @ ResourceStatus::Loading(_) => {
+ let strong_promise = RefPtr::new(promise);
+ moz_task::spawn_local("l10nfilesource_fetch_file", async move {
+ callback(
+ &strong_promise,
+ Option::<RcResource>::from(res.await).as_ref().map(|r| &**r),
+ );
+ })
+ .detach();
+ }
+ }
+}