diff options
Diffstat (limited to 'intl/l10n/rust/l10nregistry-ffi/src/source.rs')
-rw-r--r-- | intl/l10n/rust/l10nregistry-ffi/src/source.rs | 359 |
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(); + } + } +} |