/* 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, 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, _> = 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, 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, _> = 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, pre_path: &nsACString, fs: &ThinVec, 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, _> = 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, ) { 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, ) -> 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 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::::from(res.await).as_ref().map(|r| &**r), ); }) .detach(); } } }