source.rs (10087B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 use super::fetcher::{GeckoFileFetcher, MockFileFetcher}; 6 use crate::env::GeckoEnvironment; 7 8 use fluent::FluentResource; 9 use l10nregistry::source::{FileSource, FileSourceOptions, ResourceOption, ResourceStatus, RcResource}; 10 11 use nsstring::{nsACString, nsCString}; 12 use thin_vec::ThinVec; 13 use unic_langid::LanguageIdentifier; 14 15 use std::{borrow::Cow, mem, rc::Rc}; 16 use xpcom::RefPtr; 17 18 #[repr(C)] 19 pub enum L10nFileSourceStatus { 20 None, 21 EmptyName, 22 EmptyPrePath, 23 EmptyResId, 24 InvalidLocaleCode, 25 } 26 27 // For historical reasons we maintain a locale in Firefox with a codename `ja-JP-mac`. 28 // This string is an invalid BCP-47 language tag, so we don't store it in Gecko, which uses 29 // valid BCP-47 tags only, but rather keep that quirk local to Gecko L10nRegistry file fetcher. 30 // 31 // Here, if we encounter `ja-JP-mac` (invalid BCP-47), we swap it for a valid equivalent: `ja-JP-macos`. 32 // 33 // See bug 1726586 for details and fetcher::get_locale_for_gecko. 34 fn get_locale_from_gecko<'s>(input: Cow<'s, str>) -> Cow<'s, str> { 35 if input == "ja-JP-mac" { 36 "ja-JP-macos".into() 37 } else { 38 input 39 } 40 } 41 42 #[no_mangle] 43 pub extern "C" fn l10nfilesource_new( 44 name: &nsACString, 45 metasource: &nsACString, 46 locales: &ThinVec<nsCString>, 47 pre_path: &nsACString, 48 allow_override: bool, 49 status: &mut L10nFileSourceStatus, 50 ) -> *const FileSource { 51 if name.is_empty() { 52 *status = L10nFileSourceStatus::EmptyName; 53 return std::ptr::null(); 54 } 55 if pre_path.is_empty() { 56 *status = L10nFileSourceStatus::EmptyPrePath; 57 return std::ptr::null(); 58 } 59 60 let locales: Result<Vec<LanguageIdentifier>, _> = locales 61 .iter() 62 .map(|l| get_locale_from_gecko(l.to_utf8()).parse()) 63 .collect(); 64 65 let locales = match locales { 66 Ok(locales) => locales, 67 Err(..) => { 68 *status = L10nFileSourceStatus::InvalidLocaleCode; 69 return std::ptr::null(); 70 } 71 }; 72 73 let mut source = FileSource::new( 74 name.to_string(), 75 Some(metasource.to_string()), 76 locales, 77 pre_path.to_string(), 78 FileSourceOptions { allow_override }, 79 GeckoFileFetcher, 80 ); 81 source.set_reporter(GeckoEnvironment::new(None)); 82 83 *status = L10nFileSourceStatus::None; 84 Rc::into_raw(Rc::new(source)) 85 } 86 87 #[no_mangle] 88 pub unsafe extern "C" fn l10nfilesource_new_with_index( 89 name: &nsACString, 90 metasource: &nsACString, 91 locales: &ThinVec<nsCString>, 92 pre_path: &nsACString, 93 index_elements: *const nsCString, 94 index_length: usize, 95 allow_override: bool, 96 status: &mut L10nFileSourceStatus, 97 ) -> *const FileSource { 98 if name.is_empty() { 99 *status = L10nFileSourceStatus::EmptyName; 100 return std::ptr::null(); 101 } 102 if pre_path.is_empty() { 103 *status = L10nFileSourceStatus::EmptyPrePath; 104 return std::ptr::null(); 105 } 106 107 let locales: Result<Vec<LanguageIdentifier>, _> = locales 108 .iter() 109 .map(|l| get_locale_from_gecko(l.to_utf8()).parse()) 110 .collect(); 111 112 let index = if index_length > 0 { 113 assert!(!index_elements.is_null()); 114 std::slice::from_raw_parts(index_elements, index_length) 115 } else { 116 &[] 117 } 118 .into_iter() 119 .map(|s| s.to_string()) 120 .collect(); 121 122 let locales = match locales { 123 Ok(locales) => locales, 124 Err(..) => { 125 *status = L10nFileSourceStatus::InvalidLocaleCode; 126 return std::ptr::null(); 127 } 128 }; 129 130 let mut source = FileSource::new_with_index( 131 name.to_string(), 132 Some(metasource.to_string()), 133 locales, 134 pre_path.to_string(), 135 FileSourceOptions { allow_override }, 136 GeckoFileFetcher, 137 index, 138 ); 139 source.set_reporter(GeckoEnvironment::new(None)); 140 141 *status = L10nFileSourceStatus::None; 142 Rc::into_raw(Rc::new(source)) 143 } 144 145 #[repr(C)] 146 pub struct L10nFileSourceMockFile { 147 path: nsCString, 148 source: nsCString, 149 } 150 151 #[no_mangle] 152 pub extern "C" fn l10nfilesource_new_mock( 153 name: &nsACString, 154 metasource: &nsACString, 155 locales: &ThinVec<nsCString>, 156 pre_path: &nsACString, 157 fs: &ThinVec<L10nFileSourceMockFile>, 158 status: &mut L10nFileSourceStatus, 159 ) -> *const FileSource { 160 if name.is_empty() { 161 *status = L10nFileSourceStatus::EmptyName; 162 return std::ptr::null(); 163 } 164 if pre_path.is_empty() { 165 *status = L10nFileSourceStatus::EmptyPrePath; 166 return std::ptr::null(); 167 } 168 169 let locales: Result<Vec<LanguageIdentifier>, _> = locales 170 .iter() 171 .map(|l| get_locale_from_gecko(l.to_utf8()).parse()) 172 .collect(); 173 174 let locales = match locales { 175 Ok(locales) => locales, 176 Err(..) => { 177 *status = L10nFileSourceStatus::InvalidLocaleCode; 178 return std::ptr::null(); 179 } 180 }; 181 182 let fs = fs 183 .iter() 184 .map(|mock| (mock.path.to_string(), mock.source.to_string())) 185 .collect(); 186 let fetcher = MockFileFetcher::new(fs); 187 let mut source = FileSource::new( 188 name.to_string(), 189 Some(metasource.to_string()), 190 locales, 191 pre_path.to_string(), 192 Default::default(), 193 fetcher, 194 ); 195 source.set_reporter(GeckoEnvironment::new(None)); 196 197 *status = L10nFileSourceStatus::None; 198 Rc::into_raw(Rc::new(source)) 199 } 200 201 #[no_mangle] 202 pub unsafe extern "C" fn l10nfilesource_addref(source: *const FileSource) { 203 let raw = Rc::from_raw(source); 204 mem::forget(Rc::clone(&raw)); 205 mem::forget(raw); 206 } 207 208 #[no_mangle] 209 pub unsafe extern "C" fn l10nfilesource_release(source: *const FileSource) { 210 let _ = Rc::from_raw(source); 211 } 212 213 #[no_mangle] 214 pub extern "C" fn l10nfilesource_get_name(source: &FileSource, ret_val: &mut nsACString) { 215 ret_val.assign(&source.name); 216 } 217 218 #[no_mangle] 219 pub extern "C" fn l10nfilesource_get_metasource(source: &FileSource, ret_val: &mut nsACString) { 220 ret_val.assign(&source.metasource); 221 } 222 223 #[no_mangle] 224 pub extern "C" fn l10nfilesource_get_locales( 225 source: &FileSource, 226 ret_val: &mut ThinVec<nsCString>, 227 ) { 228 for locale in source.locales() { 229 ret_val.push(locale.to_string().into()); 230 } 231 } 232 233 #[no_mangle] 234 pub extern "C" fn l10nfilesource_get_prepath(source: &FileSource, ret_val: &mut nsACString) { 235 ret_val.assign(&source.pre_path); 236 } 237 238 #[no_mangle] 239 pub extern "C" fn l10nfilesource_get_index( 240 source: &FileSource, 241 ret_val: &mut ThinVec<nsCString>, 242 ) -> bool { 243 if let Some(index) = source.get_index() { 244 for entry in index { 245 ret_val.push(entry.to_string().into()); 246 } 247 true 248 } else { 249 false 250 } 251 } 252 253 #[no_mangle] 254 pub extern "C" fn l10nfilesource_has_file( 255 source: &FileSource, 256 locale: &nsACString, 257 path: &nsACString, 258 status: &mut L10nFileSourceStatus, 259 present: &mut bool, 260 ) -> bool { 261 if path.is_empty() { 262 *status = L10nFileSourceStatus::EmptyResId; 263 return false; 264 } 265 266 let locale = match locale.to_utf8().parse() { 267 Ok(locale) => locale, 268 Err(..) => { 269 *status = L10nFileSourceStatus::InvalidLocaleCode; 270 return false; 271 } 272 }; 273 274 *status = L10nFileSourceStatus::None; 275 // To work around Option<bool> we return bool for the option, 276 // and the `present` argument is the value of it. 277 if let Some(val) = source.has_file(&locale, &path.to_utf8().into()) { 278 *present = val; 279 true 280 } else { 281 false 282 } 283 } 284 285 #[no_mangle] 286 pub extern "C" fn l10nfilesource_fetch_file_sync( 287 source: &FileSource, 288 locale: &nsACString, 289 path: &nsACString, 290 status: &mut L10nFileSourceStatus, 291 ) -> *const FluentResource { 292 if path.is_empty() { 293 *status = L10nFileSourceStatus::EmptyResId; 294 return std::ptr::null(); 295 } 296 297 let locale = match locale.to_utf8().parse() { 298 Ok(locale) => locale, 299 Err(..) => { 300 *status = L10nFileSourceStatus::InvalidLocaleCode; 301 return std::ptr::null(); 302 } 303 }; 304 305 *status = L10nFileSourceStatus::None; 306 //XXX: Bug 1723191 - if we encounter a request for sync load while async load is in progress 307 // we will discard the async load and force the sync load instead. 308 // There may be a better option but we haven't had time to explore it. 309 if let ResourceOption::Some(res) = 310 source.fetch_file_sync(&locale, &path.to_utf8().into(), /* overload */ true) 311 { 312 Rc::into_raw(res) 313 } else { 314 std::ptr::null() 315 } 316 } 317 318 #[no_mangle] 319 pub unsafe extern "C" fn l10nfilesource_fetch_file( 320 source: &FileSource, 321 locale: &nsACString, 322 path: &nsACString, 323 promise: &xpcom::Promise, 324 callback: extern "C" fn(&xpcom::Promise, Option<&FluentResource>), 325 status: &mut L10nFileSourceStatus, 326 ) { 327 if path.is_empty() { 328 *status = L10nFileSourceStatus::EmptyResId; 329 return; 330 } 331 332 let locale = match locale.to_utf8().parse() { 333 Ok(locale) => locale, 334 Err(..) => { 335 *status = L10nFileSourceStatus::InvalidLocaleCode; 336 return; 337 } 338 }; 339 340 *status = L10nFileSourceStatus::None; 341 342 let path = path.to_utf8().into(); 343 344 match source.fetch_file(&locale, &path) { 345 ResourceStatus::MissingOptional => callback(promise, None), 346 ResourceStatus::MissingRequired => callback(promise, None), 347 ResourceStatus::Loaded(res) => callback(promise, Some(&res)), 348 res @ ResourceStatus::Loading(_) => { 349 let strong_promise = RefPtr::new(promise); 350 moz_task::spawn_local("l10nfilesource_fetch_file", async move { 351 callback( 352 &strong_promise, 353 Option::<RcResource>::from(res.await).as_ref().map(|r| &**r), 354 ); 355 }) 356 .detach(); 357 } 358 } 359 }