registry.rs (15603B)
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 fluent_ffi::{adapt_bundle_for_gecko, FluentBundleRc}; 6 use nsstring::{nsACString, nsCString}; 7 use std::mem; 8 use std::rc::Rc; 9 use thin_vec::ThinVec; 10 11 use crate::{env::GeckoEnvironment, fetcher::GeckoFileFetcher, xpcom_utils::is_parent_process}; 12 use fluent_fallback::{generator::BundleGenerator, types::ResourceType}; 13 use futures_channel::mpsc::{unbounded, UnboundedSender}; 14 pub use l10nregistry::{ 15 errors::L10nRegistrySetupError, 16 registry::{BundleAdapter, GenerateBundles, GenerateBundlesSync, L10nRegistry}, 17 source::{FileSource, ResourceId, ToResourceId}, 18 }; 19 use unic_langid::LanguageIdentifier; 20 use xpcom::RefPtr; 21 22 #[derive(Clone)] 23 pub struct GeckoBundleAdapter { 24 use_isolating: bool, 25 } 26 27 impl Default for GeckoBundleAdapter { 28 fn default() -> Self { 29 Self { 30 use_isolating: true, 31 } 32 } 33 } 34 35 impl BundleAdapter for GeckoBundleAdapter { 36 fn adapt_bundle(&self, bundle: &mut l10nregistry::fluent::FluentBundle) { 37 bundle.set_use_isolating(self.use_isolating); 38 adapt_bundle_for_gecko(bundle, None); 39 } 40 } 41 42 thread_local!(static L10N_REGISTRY: Rc<GeckoL10nRegistry> = { 43 let sources = if is_parent_process() { 44 let packaged_locales = get_packaged_locales(); 45 let entries = get_l10n_registry_category_entries(); 46 47 Some(entries 48 .into_iter() 49 .map(|entry| { 50 FileSource::new( 51 entry.entry.to_string(), 52 Some("app".to_string()), 53 packaged_locales.clone(), 54 entry.value.to_string(), 55 Default::default(), 56 GeckoFileFetcher, 57 ) 58 }) 59 .collect()) 60 61 } else { 62 None 63 }; 64 65 create_l10n_registry(sources) 66 }); 67 68 pub type GeckoL10nRegistry = L10nRegistry<GeckoEnvironment, GeckoBundleAdapter>; 69 pub type GeckoFluentBundleIterator = GenerateBundlesSync<GeckoEnvironment, GeckoBundleAdapter>; 70 71 trait GeckoReportError<V, E> { 72 fn report_error(self) -> Result<V, E>; 73 } 74 75 impl<V> GeckoReportError<V, L10nRegistrySetupError> for Result<V, L10nRegistrySetupError> { 76 fn report_error(self) -> Self { 77 if let Err(ref err) = self { 78 GeckoEnvironment::report_l10nregistry_setup_error(err); 79 } 80 self 81 } 82 } 83 84 #[derive(Debug)] 85 #[repr(C)] 86 pub struct L10nFileSourceDescriptor { 87 name: nsCString, 88 metasource: nsCString, 89 locales: ThinVec<nsCString>, 90 pre_path: nsCString, 91 index: ThinVec<nsCString>, 92 } 93 94 fn get_l10n_registry_category_entries() -> Vec<crate::xpcom_utils::CategoryEntry> { 95 crate::xpcom_utils::get_category_entries(&nsCString::from("l10n-registry")).unwrap_or_default() 96 } 97 98 fn get_packaged_locales() -> Vec<LanguageIdentifier> { 99 crate::xpcom_utils::get_packaged_locales() 100 .map(|locales| { 101 locales 102 .into_iter() 103 .map(|s| s.to_utf8().parse().expect("Failed to parse locale.")) 104 .collect() 105 }) 106 .unwrap_or_default() 107 } 108 109 fn create_l10n_registry(sources: Option<Vec<FileSource>>) -> Rc<GeckoL10nRegistry> { 110 let env = GeckoEnvironment::new(None); 111 let mut reg = L10nRegistry::with_provider(env); 112 113 reg.set_bundle_adapter(GeckoBundleAdapter::default()) 114 .expect("Failed to set bundle adaptation closure."); 115 116 if let Some(sources) = sources { 117 reg.register_sources(sources) 118 .expect("Failed to register sources."); 119 } 120 Rc::new(reg) 121 } 122 123 pub fn set_l10n_registry(new_sources: &ThinVec<L10nFileSourceDescriptor>) { 124 L10N_REGISTRY.with(|reg| { 125 let new_source_names: Vec<_> = new_sources 126 .iter() 127 .map(|d| d.name.to_utf8().to_string()) 128 .collect(); 129 let old_sources = reg.get_source_names().unwrap(); 130 131 let mut sources_to_be_removed = vec![]; 132 for name in &old_sources { 133 if !new_source_names.contains(&name) { 134 sources_to_be_removed.push(name); 135 } 136 } 137 reg.remove_sources(sources_to_be_removed).unwrap(); 138 139 let mut add_sources = vec![]; 140 for desc in new_sources { 141 if !old_sources.contains(&desc.name.to_string()) { 142 add_sources.push(FileSource::new( 143 desc.name.to_string(), 144 Some(desc.metasource.to_string()), 145 desc.locales 146 .iter() 147 .map(|s| s.to_utf8().parse().unwrap()) 148 .collect(), 149 desc.pre_path.to_string(), 150 Default::default(), 151 GeckoFileFetcher, 152 )); 153 } 154 } 155 reg.register_sources(add_sources).unwrap(); 156 }); 157 } 158 159 pub fn get_l10n_registry() -> Rc<GeckoL10nRegistry> { 160 L10N_REGISTRY.with(|reg| reg.clone()) 161 } 162 163 #[repr(C)] 164 #[derive(Clone, Copy)] 165 pub enum GeckoResourceType { 166 Optional, 167 Required, 168 } 169 170 #[repr(C)] 171 pub struct GeckoResourceId { 172 value: nsCString, 173 resource_type: GeckoResourceType, 174 } 175 176 impl From<&GeckoResourceId> for ResourceId { 177 fn from(resource_id: &GeckoResourceId) -> Self { 178 resource_id 179 .value 180 .to_string() 181 .to_resource_id(match resource_id.resource_type { 182 GeckoResourceType::Optional => ResourceType::Optional, 183 GeckoResourceType::Required => ResourceType::Required, 184 }) 185 } 186 } 187 188 #[repr(C)] 189 pub enum L10nRegistryStatus { 190 None, 191 EmptyName, 192 InvalidLocaleCode, 193 } 194 195 #[no_mangle] 196 pub extern "C" fn l10nregistry_new(use_isolating: bool) -> *const GeckoL10nRegistry { 197 let env = GeckoEnvironment::new(None); 198 let mut reg = L10nRegistry::with_provider(env); 199 let _ = reg 200 .set_bundle_adapter(GeckoBundleAdapter { use_isolating }) 201 .report_error(); 202 Rc::into_raw(Rc::new(reg)) 203 } 204 205 #[no_mangle] 206 pub extern "C" fn l10nregistry_instance_get() -> *const GeckoL10nRegistry { 207 let reg = get_l10n_registry(); 208 Rc::into_raw(reg) 209 } 210 211 #[no_mangle] 212 pub unsafe extern "C" fn l10nregistry_get_parent_process_sources( 213 sources: &mut ThinVec<L10nFileSourceDescriptor>, 214 ) { 215 debug_assert!( 216 is_parent_process(), 217 "This should be called only in parent process." 218 ); 219 220 // If at the point when the first content process is being initialized, the parent 221 // process `L10nRegistryService` has not been initialized yet, this will trigger it. 222 // 223 // This is architecturally imperfect, but acceptable for simplicity reasons because 224 // `L10nRegistry` instance is cheap and mainly servers as a store of state. 225 let reg = get_l10n_registry(); 226 for name in reg.get_source_names().unwrap() { 227 let source = reg.file_source_by_name(&name).unwrap().unwrap(); 228 let descriptor = L10nFileSourceDescriptor { 229 name: source.name.as_str().into(), 230 metasource: source.metasource.as_str().into(), 231 locales: source 232 .locales() 233 .iter() 234 .map(|l| l.to_string().into()) 235 .collect(), 236 pre_path: source.pre_path.as_str().into(), 237 index: source 238 .get_index() 239 .map(|index| index.into_iter().map(|s| s.into()).collect()) 240 .unwrap_or_default(), 241 }; 242 sources.push(descriptor); 243 } 244 } 245 246 #[no_mangle] 247 pub unsafe extern "C" fn l10nregistry_register_parent_process_sources( 248 sources: &ThinVec<L10nFileSourceDescriptor>, 249 ) { 250 debug_assert!( 251 !is_parent_process(), 252 "This should be called only in content process." 253 ); 254 set_l10n_registry(sources); 255 } 256 257 #[no_mangle] 258 pub unsafe extern "C" fn l10nregistry_addref(reg: *const GeckoL10nRegistry) { 259 let raw = Rc::from_raw(reg); 260 mem::forget(Rc::clone(&raw)); 261 mem::forget(raw); 262 } 263 264 #[no_mangle] 265 pub unsafe extern "C" fn l10nregistry_release(reg: *const GeckoL10nRegistry) { 266 let _ = Rc::from_raw(reg); 267 } 268 269 #[no_mangle] 270 pub extern "C" fn l10nregistry_get_available_locales( 271 reg: &GeckoL10nRegistry, 272 result: &mut ThinVec<nsCString>, 273 ) { 274 if let Ok(locales) = reg.get_available_locales().report_error() { 275 result.extend(locales.into_iter().map(|locale| locale.to_string().into())); 276 } 277 } 278 279 fn broadcast_settings_if_parent(reg: &GeckoL10nRegistry) { 280 if !is_parent_process() { 281 return; 282 } 283 284 L10N_REGISTRY.with(|reg_service| { 285 if std::ptr::eq(Rc::as_ptr(reg_service), reg) { 286 let locales = reg 287 .get_available_locales() 288 .unwrap() 289 .iter() 290 .map(|loc| loc.to_string().into()) 291 .collect(); 292 293 unsafe { 294 crate::xpcom_utils::set_available_locales(&locales); 295 L10nRegistrySendUpdateL10nFileSources(); 296 } 297 } 298 }); 299 } 300 301 #[no_mangle] 302 pub extern "C" fn l10nregistry_register_sources( 303 reg: &GeckoL10nRegistry, 304 sources: &ThinVec<&FileSource>, 305 ) { 306 let _ = reg 307 .register_sources(sources.iter().map(|&s| s.clone()).collect()) 308 .report_error(); 309 310 broadcast_settings_if_parent(reg); 311 } 312 313 #[no_mangle] 314 pub extern "C" fn l10nregistry_update_sources( 315 reg: &GeckoL10nRegistry, 316 sources: &mut ThinVec<&FileSource>, 317 ) { 318 let _ = reg 319 .update_sources(sources.iter().map(|&s| s.clone()).collect()) 320 .report_error(); 321 broadcast_settings_if_parent(reg); 322 } 323 324 #[no_mangle] 325 pub unsafe extern "C" fn l10nregistry_remove_sources( 326 reg: &GeckoL10nRegistry, 327 sources_elements: *const nsCString, 328 sources_length: usize, 329 ) { 330 if sources_elements.is_null() { 331 return; 332 } 333 334 let sources = std::slice::from_raw_parts(sources_elements, sources_length); 335 let _ = reg.remove_sources(sources.to_vec()).report_error(); 336 broadcast_settings_if_parent(reg); 337 } 338 339 #[no_mangle] 340 pub extern "C" fn l10nregistry_has_source( 341 reg: &GeckoL10nRegistry, 342 name: &nsACString, 343 status: &mut L10nRegistryStatus, 344 ) -> bool { 345 if name.is_empty() { 346 *status = L10nRegistryStatus::EmptyName; 347 return false; 348 } 349 *status = L10nRegistryStatus::None; 350 reg.has_source(&name.to_utf8()) 351 .report_error() 352 .unwrap_or(false) 353 } 354 355 #[no_mangle] 356 pub extern "C" fn l10nregistry_get_source( 357 reg: &GeckoL10nRegistry, 358 name: &nsACString, 359 status: &mut L10nRegistryStatus, 360 ) -> *mut FileSource { 361 if name.is_empty() { 362 *status = L10nRegistryStatus::EmptyName; 363 return std::ptr::null_mut(); 364 } 365 366 *status = L10nRegistryStatus::None; 367 368 if let Ok(Some(source)) = reg.file_source_by_name(&name.to_utf8()).report_error() { 369 Box::into_raw(Box::new(source)) 370 } else { 371 std::ptr::null_mut() 372 } 373 } 374 375 #[no_mangle] 376 pub extern "C" fn l10nregistry_clear_sources(reg: &GeckoL10nRegistry) { 377 let _ = reg.clear_sources().report_error(); 378 379 broadcast_settings_if_parent(reg); 380 } 381 382 #[no_mangle] 383 pub extern "C" fn l10nregistry_get_source_names( 384 reg: &GeckoL10nRegistry, 385 result: &mut ThinVec<nsCString>, 386 ) { 387 if let Ok(names) = reg.get_source_names().report_error() { 388 result.extend(names.into_iter().map(|name| nsCString::from(name))); 389 } 390 } 391 392 #[no_mangle] 393 pub unsafe extern "C" fn l10nregistry_generate_bundles_sync( 394 reg: &GeckoL10nRegistry, 395 locales_elements: *const nsCString, 396 locales_length: usize, 397 res_ids_elements: *const GeckoResourceId, 398 res_ids_length: usize, 399 status: &mut L10nRegistryStatus, 400 ) -> *mut GeckoFluentBundleIterator { 401 let locales = std::slice::from_raw_parts(locales_elements, locales_length); 402 let res_ids = std::slice::from_raw_parts(res_ids_elements, res_ids_length) 403 .into_iter() 404 .map(ResourceId::from) 405 .collect(); 406 let locales: Result<Vec<LanguageIdentifier>, _> = 407 locales.into_iter().map(|s| s.to_utf8().parse()).collect(); 408 409 match locales { 410 Ok(locales) => { 411 *status = L10nRegistryStatus::None; 412 let iter = reg.bundles_iter(locales.into_iter(), res_ids); 413 Box::into_raw(Box::new(iter)) 414 } 415 Err(_) => { 416 *status = L10nRegistryStatus::InvalidLocaleCode; 417 std::ptr::null_mut() 418 } 419 } 420 } 421 422 #[no_mangle] 423 pub unsafe extern "C" fn fluent_bundle_iterator_destroy(iter: *mut GeckoFluentBundleIterator) { 424 let _ = Box::from_raw(iter); 425 } 426 427 #[no_mangle] 428 pub extern "C" fn fluent_bundle_iterator_next( 429 iter: &mut GeckoFluentBundleIterator, 430 ) -> *mut FluentBundleRc { 431 if let Some(Ok(result)) = iter.next() { 432 Box::into_raw(Box::new(result)) 433 } else { 434 std::ptr::null_mut() 435 } 436 } 437 438 pub struct NextRequest { 439 promise: RefPtr<xpcom::Promise>, 440 // Ownership is transferred here. 441 callback: unsafe extern "C" fn(&xpcom::Promise, *mut FluentBundleRc), 442 } 443 444 pub struct GeckoFluentBundleAsyncIteratorWrapper(UnboundedSender<NextRequest>); 445 446 #[no_mangle] 447 pub unsafe extern "C" fn l10nregistry_generate_bundles( 448 reg: &GeckoL10nRegistry, 449 locales_elements: *const nsCString, 450 locales_length: usize, 451 res_ids_elements: *const GeckoResourceId, 452 res_ids_length: usize, 453 status: &mut L10nRegistryStatus, 454 ) -> *mut GeckoFluentBundleAsyncIteratorWrapper { 455 let locales = std::slice::from_raw_parts(locales_elements, locales_length); 456 let res_ids = std::slice::from_raw_parts(res_ids_elements, res_ids_length) 457 .into_iter() 458 .map(ResourceId::from) 459 .collect(); 460 let locales: Result<Vec<LanguageIdentifier>, _> = 461 locales.into_iter().map(|s| s.to_utf8().parse()).collect(); 462 463 match locales { 464 Ok(locales) => { 465 *status = L10nRegistryStatus::None; 466 let mut iter = reg.bundles_stream(locales.into_iter(), res_ids); 467 468 // Immediately spawn the task which will handle the async calls, and use an `UnboundedSender` 469 // to send callbacks for specific `next()` calls to it. 470 let (sender, mut receiver) = unbounded::<NextRequest>(); 471 moz_task::spawn_local("l10nregistry_generate_bundles", async move { 472 use futures::StreamExt; 473 while let Some(req) = receiver.next().await { 474 let result = match iter.next().await { 475 Some(Ok(result)) => Box::into_raw(Box::new(result)), 476 _ => std::ptr::null_mut(), 477 }; 478 (req.callback)(&req.promise, result); 479 } 480 }) 481 .detach(); 482 let iter = GeckoFluentBundleAsyncIteratorWrapper(sender); 483 Box::into_raw(Box::new(iter)) 484 } 485 Err(_) => { 486 *status = L10nRegistryStatus::InvalidLocaleCode; 487 std::ptr::null_mut() 488 } 489 } 490 } 491 492 #[no_mangle] 493 pub unsafe extern "C" fn fluent_bundle_async_iterator_destroy( 494 iter: *mut GeckoFluentBundleAsyncIteratorWrapper, 495 ) { 496 let _ = Box::from_raw(iter); 497 } 498 499 #[no_mangle] 500 pub extern "C" fn fluent_bundle_async_iterator_next( 501 iter: &GeckoFluentBundleAsyncIteratorWrapper, 502 promise: &xpcom::Promise, 503 callback: extern "C" fn(&xpcom::Promise, *mut FluentBundleRc), 504 ) { 505 if iter 506 .0 507 .unbounded_send(NextRequest { 508 promise: RefPtr::new(promise), 509 callback, 510 }) 511 .is_err() 512 { 513 callback(promise, std::ptr::null_mut()); 514 } 515 } 516 517 extern "C" { 518 pub fn L10nRegistrySendUpdateL10nFileSources(); 519 }