tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }