Source code

Revision control

Copy as Markdown

Other Tools

/* 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();
}
}
}