tor-browser

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

lib.rs (19127B)


      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::FluentValue;
      6 use fluent_fallback::{
      7    types::{
      8        L10nAttribute as FluentL10nAttribute, L10nKey as FluentL10nKey,
      9        L10nMessage as FluentL10nMessage, ResourceId,
     10    },
     11    Localization,
     12 };
     13 use fluent_ffi::{convert_args, FluentArgs, FluentArgument, L10nArg};
     14 use l10nregistry_ffi::{
     15    env::GeckoEnvironment,
     16    registry::{get_l10n_registry, GeckoL10nRegistry, GeckoResourceId},
     17 };
     18 use nsstring::{nsACString, nsCString};
     19 use std::os::raw::c_void;
     20 use std::{borrow::Cow, cell::RefCell};
     21 use thin_vec::ThinVec;
     22 use unic_langid::LanguageIdentifier;
     23 use xpcom::{
     24    interfaces::{nsIRunnablePriority},
     25    RefCounted, RefPtr, Refcnt,
     26 };
     27 
     28 #[derive(Debug)]
     29 #[repr(C)]
     30 pub struct L10nKey<'s> {
     31    id: &'s nsACString,
     32    args: ThinVec<L10nArg<'s>>,
     33 }
     34 
     35 impl<'s> From<&'s L10nKey<'s>> for FluentL10nKey<'static> {
     36    fn from(input: &'s L10nKey<'s>) -> Self {
     37        FluentL10nKey {
     38            id: input.id.to_utf8().to_string().into(),
     39            args: convert_args_to_owned(&input.args),
     40        }
     41    }
     42 }
     43 
     44 // This is a variant of `convert_args` from `fluent-ffi` with a 'static constrain
     45 // put on the resulting `FluentArgs` to make it acceptable into `spqwn_current_thread`.
     46 pub fn convert_args_to_owned(args: &[L10nArg]) -> Option<FluentArgs<'static>> {
     47    if args.is_empty() {
     48        return None;
     49    }
     50 
     51    let mut result = FluentArgs::with_capacity(args.len());
     52    for arg in args {
     53        let val = match arg.value {
     54            FluentArgument::Double_(d) => FluentValue::from(d),
     55            // We need this to be owned because we pass the result into `spawn_local`.
     56            FluentArgument::String(s) => FluentValue::from(Cow::Owned(s.to_utf8().to_string())),
     57        };
     58        result.set(arg.id.to_string(), val);
     59    }
     60    Some(result)
     61 }
     62 
     63 #[derive(Debug)]
     64 #[repr(C)]
     65 pub struct L10nAttribute {
     66    name: nsCString,
     67    value: nsCString,
     68 }
     69 
     70 impl From<FluentL10nAttribute<'_>> for L10nAttribute {
     71    fn from(attr: FluentL10nAttribute<'_>) -> Self {
     72        Self {
     73            name: nsCString::from(&*attr.name),
     74            value: nsCString::from(&*attr.value),
     75        }
     76    }
     77 }
     78 
     79 #[derive(Debug)]
     80 #[repr(C)]
     81 pub struct L10nMessage {
     82    value: nsCString,
     83    attributes: ThinVec<L10nAttribute>,
     84 }
     85 
     86 impl std::default::Default for L10nMessage {
     87    fn default() -> Self {
     88        Self {
     89            value: nsCString::new(),
     90            attributes: ThinVec::new(),
     91        }
     92    }
     93 }
     94 
     95 #[derive(Debug)]
     96 #[repr(C)]
     97 pub struct OptionalL10nMessage {
     98    is_present: bool,
     99    message: L10nMessage,
    100 }
    101 
    102 impl From<FluentL10nMessage<'_>> for L10nMessage {
    103    fn from(input: FluentL10nMessage) -> Self {
    104        let value = if let Some(value) = input.value {
    105            value.to_string().into()
    106        } else {
    107            let mut s = nsCString::new();
    108            s.set_is_void(true);
    109            s
    110        };
    111        Self {
    112            value,
    113            attributes: input.attributes.into_iter().map(Into::into).collect(),
    114        }
    115    }
    116 }
    117 
    118 pub struct LocalizationRc {
    119    inner: RefCell<Localization<GeckoL10nRegistry, GeckoEnvironment>>,
    120    refcnt: Refcnt,
    121 }
    122 
    123 // xpcom::RefPtr support
    124 unsafe impl RefCounted for LocalizationRc {
    125    unsafe fn addref(&self) {
    126        localization_addref(self);
    127    }
    128    unsafe fn release(&self) {
    129        localization_release(self);
    130    }
    131 }
    132 
    133 impl LocalizationRc {
    134    pub fn new(
    135        res_ids: Vec<ResourceId>,
    136        is_sync: bool,
    137        registry: Option<&GeckoL10nRegistry>,
    138        locales: Option<Vec<LanguageIdentifier>>,
    139    ) -> RefPtr<Self> {
    140        let env = GeckoEnvironment::new(locales);
    141        let inner = if let Some(reg) = registry {
    142            Localization::with_env(res_ids, is_sync, env, reg.clone())
    143        } else {
    144            let reg = (*get_l10n_registry()).clone();
    145            Localization::with_env(res_ids, is_sync, env, reg)
    146        };
    147 
    148        let loc = Box::new(LocalizationRc {
    149            inner: RefCell::new(inner),
    150            refcnt: unsafe { Refcnt::new() },
    151        });
    152 
    153        unsafe {
    154            RefPtr::from_raw(Box::into_raw(loc))
    155                .expect("Failed to create RefPtr<LocalizationRc> from Box<LocalizationRc>")
    156        }
    157    }
    158 
    159    pub fn add_resource_id(&self, res_id: ResourceId) {
    160        self.inner.borrow_mut().add_resource_id(res_id);
    161    }
    162 
    163    pub fn add_resource_ids(&self, res_ids: Vec<ResourceId>) {
    164        self.inner.borrow_mut().add_resource_ids(res_ids);
    165    }
    166 
    167    pub fn remove_resource_id(&self, res_id: ResourceId) -> usize {
    168        self.inner.borrow_mut().remove_resource_id(res_id)
    169    }
    170 
    171    pub fn remove_resource_ids(&self, res_ids: Vec<ResourceId>) -> usize {
    172        self.inner.borrow_mut().remove_resource_ids(res_ids)
    173    }
    174 
    175    pub fn set_async(&self) {
    176        if self.is_sync() {
    177            self.inner.borrow_mut().set_async();
    178        }
    179    }
    180 
    181    pub fn is_sync(&self) -> bool {
    182        self.inner.borrow().is_sync()
    183    }
    184 
    185    pub fn on_change(&self) {
    186        self.inner.borrow_mut().on_change();
    187    }
    188 
    189    pub fn format_value_sync(
    190        &self,
    191        id: &nsACString,
    192        args: &ThinVec<L10nArg>,
    193        ret_val: &mut nsACString,
    194        ret_err: &mut ThinVec<nsCString>,
    195    ) -> bool {
    196        let mut errors = vec![];
    197        let args = convert_args(&args);
    198        if let Ok(value) = self.inner.borrow().bundles().format_value_sync(
    199            &id.to_utf8(),
    200            args.as_ref(),
    201            &mut errors,
    202        ) {
    203            if let Some(value) = value {
    204                ret_val.assign(&value);
    205            } else {
    206                ret_val.set_is_void(true);
    207            }
    208            #[cfg(debug_assertions)]
    209            debug_assert_variables_exist(&errors, &[id], |id| id.to_string());
    210            ret_err.extend(errors.into_iter().map(|err| err.to_string().into()));
    211            true
    212        } else {
    213            false
    214        }
    215    }
    216 
    217    pub fn format_values_sync(
    218        &self,
    219        keys: &ThinVec<L10nKey>,
    220        ret_val: &mut ThinVec<nsCString>,
    221        ret_err: &mut ThinVec<nsCString>,
    222    ) -> bool {
    223        ret_val.reserve(keys.len());
    224        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();
    225        let mut errors = vec![];
    226        if let Ok(values) = self
    227            .inner
    228            .borrow()
    229            .bundles()
    230            .format_values_sync(&keys, &mut errors)
    231        {
    232            for value in values.iter() {
    233                if let Some(value) = value {
    234                    ret_val.push(value.as_ref().into());
    235                } else {
    236                    let mut void_string = nsCString::new();
    237                    void_string.set_is_void(true);
    238                    ret_val.push(void_string);
    239                }
    240            }
    241            #[cfg(debug_assertions)]
    242            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());
    243            ret_err.extend(errors.into_iter().map(|err| err.to_string().into()));
    244            true
    245        } else {
    246            false
    247        }
    248    }
    249 
    250    pub fn format_messages_sync(
    251        &self,
    252        keys: &ThinVec<L10nKey>,
    253        ret_val: &mut ThinVec<OptionalL10nMessage>,
    254        ret_err: &mut ThinVec<nsCString>,
    255    ) -> bool {
    256        ret_val.reserve(keys.len());
    257        let mut errors = vec![];
    258        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();
    259        if let Ok(messages) = self
    260            .inner
    261            .borrow()
    262            .bundles()
    263            .format_messages_sync(&keys, &mut errors)
    264        {
    265            for msg in messages {
    266                ret_val.push(if let Some(msg) = msg {
    267                    OptionalL10nMessage {
    268                        is_present: true,
    269                        message: msg.into(),
    270                    }
    271                } else {
    272                    OptionalL10nMessage {
    273                        is_present: false,
    274                        message: L10nMessage::default(),
    275                    }
    276                });
    277            }
    278            assert_eq!(keys.len(), ret_val.len());
    279            #[cfg(debug_assertions)]
    280            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());
    281            ret_err.extend(errors.into_iter().map(|err| err.to_string().into()));
    282            true
    283        } else {
    284            false
    285        }
    286    }
    287 
    288    pub fn format_value(
    289        &self,
    290        id: &nsACString,
    291        args: &ThinVec<L10nArg>,
    292        promise: &xpcom::Promise,
    293        callback: extern "C" fn(&xpcom::Promise, &nsACString, &ThinVec<nsCString>),
    294    ) {
    295        let bundles = self.inner.borrow().bundles().clone();
    296 
    297        let args = convert_args_to_owned(&args);
    298 
    299        let id = nsCString::from(id);
    300        let strong_promise = RefPtr::new(promise);
    301 
    302        moz_task::TaskBuilder::new("LocalizationRc::format_value", async move {
    303            let mut errors = vec![];
    304            let value = if let Some(value) = bundles
    305                .format_value(&id.to_utf8(), args.as_ref(), &mut errors)
    306                .await
    307            {
    308                let v: nsCString = value.to_string().into();
    309                v
    310            } else {
    311                let mut v = nsCString::new();
    312                v.set_is_void(true);
    313                v
    314            };
    315            #[cfg(debug_assertions)]
    316            debug_assert_variables_exist(&errors, &[id], |id| id.to_string());
    317            let errors = errors
    318                .into_iter()
    319                .map(|err| err.to_string().into())
    320                .collect();
    321            callback(&strong_promise, &value, &errors);
    322        })
    323        .priority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING as u32)
    324        .spawn_local()
    325        .detach();
    326    }
    327 
    328    pub fn format_values(
    329        &self,
    330        keys: &ThinVec<L10nKey>,
    331        promise: &xpcom::Promise,
    332        callback: extern "C" fn(&xpcom::Promise, &ThinVec<nsCString>, &ThinVec<nsCString>),
    333    ) {
    334        let bundles = self.inner.borrow().bundles().clone();
    335 
    336        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();
    337 
    338        let strong_promise = RefPtr::new(promise);
    339 
    340        moz_task::TaskBuilder::new("LocalizationRc::format_values", async move {
    341            let mut errors = vec![];
    342            let ret_val = bundles
    343                .format_values(&keys, &mut errors)
    344                .await
    345                .into_iter()
    346                .map(|value| {
    347                    if let Some(value) = value {
    348                        nsCString::from(value.as_ref())
    349                    } else {
    350                        let mut v = nsCString::new();
    351                        v.set_is_void(true);
    352                        v
    353                    }
    354                })
    355                .collect::<ThinVec<_>>();
    356 
    357            assert_eq!(keys.len(), ret_val.len());
    358 
    359            #[cfg(debug_assertions)]
    360            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());
    361            let errors = errors
    362                .into_iter()
    363                .map(|err| err.to_string().into())
    364                .collect();
    365 
    366            callback(&strong_promise, &ret_val, &errors);
    367        })
    368        .priority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING as u32)
    369        .spawn_local()
    370        .detach();
    371    }
    372 
    373    pub fn format_messages(
    374        &self,
    375        keys: &ThinVec<L10nKey>,
    376        promise: &xpcom::Promise,
    377        callback: extern "C" fn(
    378            &xpcom::Promise,
    379            &ThinVec<OptionalL10nMessage>,
    380            &ThinVec<nsCString>,
    381        ),
    382    ) {
    383        let bundles = self.inner.borrow().bundles().clone();
    384 
    385        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();
    386 
    387        let strong_promise = RefPtr::new(promise);
    388 
    389        moz_task::TaskBuilder::new("LocalizationRc::format_messages", async move {
    390            let mut errors = vec![];
    391            let ret_val = bundles
    392                .format_messages(&keys, &mut errors)
    393                .await
    394                .into_iter()
    395                .map(|msg| {
    396                    if let Some(msg) = msg {
    397                        OptionalL10nMessage {
    398                            is_present: true,
    399                            message: msg.into(),
    400                        }
    401                    } else {
    402                        OptionalL10nMessage {
    403                            is_present: false,
    404                            message: L10nMessage::default(),
    405                        }
    406                    }
    407                })
    408                .collect::<ThinVec<_>>();
    409 
    410            assert_eq!(keys.len(), ret_val.len());
    411 
    412            #[cfg(debug_assertions)]
    413            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());
    414 
    415            let errors = errors
    416                .into_iter()
    417                .map(|err| err.to_string().into())
    418                .collect();
    419 
    420            callback(&strong_promise, &ret_val, &errors);
    421        })
    422        .priority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING as u32)
    423        .spawn_local()
    424        .detach();
    425    }
    426 }
    427 
    428 #[no_mangle]
    429 pub extern "C" fn localization_parse_locale(input: &nsCString) -> *const c_void {
    430    let l: LanguageIdentifier = input.to_utf8().parse().unwrap();
    431    Box::into_raw(Box::new(l)) as *const c_void
    432 }
    433 
    434 #[no_mangle]
    435 pub extern "C" fn localization_new(
    436    res_ids: &ThinVec<GeckoResourceId>,
    437    is_sync: bool,
    438    reg: Option<&GeckoL10nRegistry>,
    439    result: &mut *const LocalizationRc,
    440 ) {
    441    *result = std::ptr::null();
    442    let res_ids: Vec<ResourceId> = res_ids.iter().map(ResourceId::from).collect();
    443    *result = RefPtr::forget_into_raw(LocalizationRc::new(res_ids, is_sync, reg, None));
    444 }
    445 
    446 #[no_mangle]
    447 pub extern "C" fn localization_new_with_locales(
    448    res_ids: &ThinVec<GeckoResourceId>,
    449    is_sync: bool,
    450    reg: Option<&GeckoL10nRegistry>,
    451    locales: Option<&ThinVec<nsCString>>,
    452    result: &mut *const LocalizationRc,
    453 ) -> bool {
    454    *result = std::ptr::null();
    455    let res_ids: Vec<ResourceId> = res_ids.iter().map(ResourceId::from).collect();
    456    let locales: Result<Option<Vec<LanguageIdentifier>>, _> = locales
    457        .map(|locales| {
    458            locales
    459                .iter()
    460                .map(|s| LanguageIdentifier::from_bytes(&s))
    461                .collect()
    462        })
    463        .transpose();
    464 
    465    if let Ok(locales) = locales {
    466        *result = RefPtr::forget_into_raw(LocalizationRc::new(res_ids, is_sync, reg, locales));
    467        true
    468    } else {
    469        false
    470    }
    471 }
    472 
    473 #[no_mangle]
    474 pub unsafe extern "C" fn localization_addref(loc: &LocalizationRc) {
    475    loc.refcnt.inc();
    476 }
    477 
    478 #[no_mangle]
    479 pub unsafe extern "C" fn localization_release(loc: *const LocalizationRc) {
    480    let rc = (*loc).refcnt.dec();
    481    if rc == 0 {
    482        std::mem::drop(Box::from_raw(loc as *const _ as *mut LocalizationRc));
    483    }
    484 }
    485 
    486 #[no_mangle]
    487 pub extern "C" fn localization_add_res_id(loc: &LocalizationRc, res_id: &GeckoResourceId) {
    488    loc.add_resource_id(res_id.into());
    489 }
    490 
    491 #[no_mangle]
    492 pub extern "C" fn localization_add_res_ids(loc: &LocalizationRc, res_ids: &ThinVec<GeckoResourceId>) {
    493    let res_ids = res_ids.iter().map(ResourceId::from).collect();
    494    loc.add_resource_ids(res_ids);
    495 }
    496 
    497 #[no_mangle]
    498 pub extern "C" fn localization_remove_res_id(loc: &LocalizationRc, res_id: &GeckoResourceId) -> usize {
    499    loc.remove_resource_id(res_id.into())
    500 }
    501 
    502 #[no_mangle]
    503 pub extern "C" fn localization_remove_res_ids(
    504    loc: &LocalizationRc,
    505    res_ids: &ThinVec<GeckoResourceId>,
    506 ) -> usize {
    507    let res_ids = res_ids.iter().map(ResourceId::from).collect();
    508    loc.remove_resource_ids(res_ids)
    509 }
    510 
    511 #[no_mangle]
    512 pub extern "C" fn localization_format_value_sync(
    513    loc: &LocalizationRc,
    514    id: &nsACString,
    515    args: &ThinVec<L10nArg>,
    516    ret_val: &mut nsACString,
    517    ret_err: &mut ThinVec<nsCString>,
    518 ) -> bool {
    519    loc.format_value_sync(id, args, ret_val, ret_err)
    520 }
    521 
    522 #[no_mangle]
    523 pub extern "C" fn localization_format_values_sync(
    524    loc: &LocalizationRc,
    525    keys: &ThinVec<L10nKey>,
    526    ret_val: &mut ThinVec<nsCString>,
    527    ret_err: &mut ThinVec<nsCString>,
    528 ) -> bool {
    529    loc.format_values_sync(keys, ret_val, ret_err)
    530 }
    531 
    532 #[no_mangle]
    533 pub extern "C" fn localization_format_messages_sync(
    534    loc: &LocalizationRc,
    535    keys: &ThinVec<L10nKey>,
    536    ret_val: &mut ThinVec<OptionalL10nMessage>,
    537    ret_err: &mut ThinVec<nsCString>,
    538 ) -> bool {
    539    loc.format_messages_sync(keys, ret_val, ret_err)
    540 }
    541 
    542 #[no_mangle]
    543 pub extern "C" fn localization_format_value(
    544    loc: &LocalizationRc,
    545    id: &nsACString,
    546    args: &ThinVec<L10nArg>,
    547    promise: &xpcom::Promise,
    548    callback: extern "C" fn(&xpcom::Promise, &nsACString, &ThinVec<nsCString>),
    549 ) {
    550    loc.format_value(id, args, promise, callback);
    551 }
    552 
    553 #[no_mangle]
    554 pub extern "C" fn localization_format_values(
    555    loc: &LocalizationRc,
    556    keys: &ThinVec<L10nKey>,
    557    promise: &xpcom::Promise,
    558    callback: extern "C" fn(&xpcom::Promise, &ThinVec<nsCString>, &ThinVec<nsCString>),
    559 ) {
    560    loc.format_values(keys, promise, callback);
    561 }
    562 
    563 #[no_mangle]
    564 pub extern "C" fn localization_format_messages(
    565    loc: &LocalizationRc,
    566    keys: &ThinVec<L10nKey>,
    567    promise: &xpcom::Promise,
    568    callback: extern "C" fn(&xpcom::Promise, &ThinVec<OptionalL10nMessage>, &ThinVec<nsCString>),
    569 ) {
    570    loc.format_messages(keys, promise, callback);
    571 }
    572 
    573 #[no_mangle]
    574 pub extern "C" fn localization_set_async(loc: &LocalizationRc) {
    575    loc.set_async();
    576 }
    577 
    578 #[no_mangle]
    579 pub extern "C" fn localization_is_sync(loc: &LocalizationRc) -> bool {
    580    loc.is_sync()
    581 }
    582 
    583 #[no_mangle]
    584 pub extern "C" fn localization_on_change(loc: &LocalizationRc) {
    585    loc.on_change();
    586 }
    587 
    588 #[cfg(debug_assertions)]
    589 fn debug_assert_variables_exist<K, F>(
    590    errors: &[fluent_fallback::LocalizationError],
    591    keys: &[K],
    592    to_string: F,
    593 ) where
    594    F: Fn(&K) -> String,
    595 {
    596    for error in errors {
    597        if let fluent_fallback::LocalizationError::Resolver { errors, .. } = error {
    598            use fluent::{
    599                resolver::{errors::ReferenceKind, ResolverError},
    600                FluentError,
    601            };
    602            for error in errors {
    603                if let FluentError::ResolverError(ResolverError::Reference(
    604                    ReferenceKind::Variable { id },
    605                )) = error
    606                {
    607                    // This error needs to be actionable for Firefox engineers to fix
    608                    // their Fluent issues. It might be nicer to share the specific
    609                    // message, but at this point we don't have that information.
    610                    eprintln!(
    611                        "Fluent error, the argument \"${}\" was not provided a value.",
    612                        id
    613                    );
    614                    eprintln!("This error happened while formatting the following messages:");
    615                    for key in keys {
    616                        eprintln!("  {:?}", to_string(key))
    617                    }
    618 
    619                    // Panic with the slightly more cryptic ResolverError.
    620                    panic!("{}", error.to_string());
    621                }
    622            }
    623        }
    624    }
    625 }