tor-browser

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

bundle.rs (9869B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use crate::builtins::{FluentDateTime, FluentDateTimeOptions, NumberFormat};
      6 use cstr::cstr;
      7 pub use fluent::{FluentArgs, FluentBundle, FluentError, FluentResource, FluentValue};
      8 use fluent_pseudo::transform_dom;
      9 pub use intl_memoizer::IntlLangMemoizer;
     10 use nsstring::{nsACString, nsCString};
     11 use std::borrow::Cow;
     12 use std::ffi::CStr;
     13 use std::mem;
     14 use std::rc::Rc;
     15 use thin_vec::ThinVec;
     16 use unic_langid::LanguageIdentifier;
     17 use xpcom::interfaces::nsIPrefBranch;
     18 
     19 pub type FluentBundleRc = FluentBundle<Rc<FluentResource>>;
     20 
     21 #[derive(Debug)]
     22 #[repr(C, u8)]
     23 pub enum FluentArgument<'s> {
     24    Double_(f64),
     25    String(&'s nsACString),
     26 }
     27 
     28 #[derive(Debug)]
     29 #[repr(C)]
     30 pub struct L10nArg<'s> {
     31    pub id: &'s nsACString,
     32    pub value: FluentArgument<'s>,
     33 }
     34 
     35 fn transform_accented(s: &str) -> Cow<'_, str> {
     36    transform_dom(s, false, true, true)
     37 }
     38 
     39 fn transform_bidi(s: &str) -> Cow<'_, str> {
     40    transform_dom(s, false, false, false)
     41 }
     42 
     43 fn format_numbers(num: &FluentValue, intls: &IntlLangMemoizer) -> Option<String> {
     44    match num {
     45        FluentValue::Number(n) => {
     46            let result = intls
     47                .with_try_get::<NumberFormat, _, _>((n.options.clone(),), |nf| nf.format(n.value))
     48                .expect("Failed to retrieve a NumberFormat instance.");
     49            Some(result)
     50        }
     51        _ => None,
     52    }
     53 }
     54 
     55 fn get_string_pref(name: &CStr) -> Option<nsCString> {
     56    let mut value = nsCString::new();
     57    let prefs_service =
     58        xpcom::get_service::<nsIPrefBranch>(cstr!("@mozilla.org/preferences-service;1"))?;
     59    unsafe {
     60        prefs_service
     61            .GetCharPref(name.as_ptr(), &mut *value)
     62            .to_result()
     63            .ok()?;
     64    }
     65    Some(value)
     66 }
     67 
     68 fn get_bool_pref(name: &CStr) -> Option<bool> {
     69    let mut value = false;
     70    let prefs_service =
     71        xpcom::get_service::<nsIPrefBranch>(cstr!("@mozilla.org/preferences-service;1"))?;
     72    unsafe {
     73        prefs_service
     74            .GetBoolPref(name.as_ptr(), &mut value)
     75            .to_result()
     76            .ok()?;
     77    }
     78    Some(value)
     79 }
     80 
     81 pub fn adapt_bundle_for_gecko(bundle: &mut FluentBundleRc, pseudo_strategy: Option<&nsACString>) {
     82    bundle.set_formatter(Some(format_numbers));
     83 
     84    bundle
     85        .add_function("PLATFORM", |_args, _named_args| {
     86            if cfg!(target_os = "linux") {
     87                "linux".into()
     88            } else if cfg!(target_os = "windows") {
     89                "windows".into()
     90            } else if cfg!(target_os = "macos") {
     91                "macos".into()
     92            } else if cfg!(target_os = "android") {
     93                "android".into()
     94            } else {
     95                "other".into()
     96            }
     97        })
     98        .expect("Failed to add a function to the bundle.");
     99    bundle
    100        .add_function("NUMBER", |args, named| {
    101            if let Some(FluentValue::Number(n)) = args.get(0) {
    102                let mut num = n.clone();
    103                num.options.merge(named);
    104                FluentValue::Number(num)
    105            } else {
    106                FluentValue::None
    107            }
    108        })
    109        .expect("Failed to add a function to the bundle.");
    110    bundle
    111        .add_function("DATETIME", |args, named| {
    112            if let Some(FluentValue::Number(n)) = args.get(0) {
    113                let mut options = FluentDateTimeOptions::default();
    114                options.merge(&named);
    115                FluentValue::Custom(Box::new(FluentDateTime::new(n.value, options)))
    116            } else {
    117                FluentValue::None
    118            }
    119        })
    120        .expect("Failed to add a function to the bundle.");
    121 
    122    enum PseudoStrategy {
    123        Accented,
    124        Bidi,
    125        None,
    126    }
    127    // This is quirky because we can't coerce Option<&nsACString> and Option<nsCString>
    128    // into bytes easily without allocating.
    129    let strategy_kind = match pseudo_strategy.map(|s| &s[..]) {
    130        Some(b"accented") => PseudoStrategy::Accented,
    131        Some(b"bidi") => PseudoStrategy::Bidi,
    132        _ => {
    133            if let Some(pseudo_strategy) = get_string_pref(cstr!("intl.l10n.pseudo")) {
    134                match &pseudo_strategy[..] {
    135                    b"accented" => PseudoStrategy::Accented,
    136                    b"bidi" => PseudoStrategy::Bidi,
    137                    _ => PseudoStrategy::None,
    138                }
    139            } else {
    140                PseudoStrategy::None
    141            }
    142        }
    143    };
    144    match strategy_kind {
    145        PseudoStrategy::Accented => bundle.set_transform(Some(transform_accented)),
    146        PseudoStrategy::Bidi => bundle.set_transform(Some(transform_bidi)),
    147        PseudoStrategy::None => bundle.set_transform(None),
    148    }
    149 
    150    // Temporarily disable bidi isolation due to Microsoft not supporting FSI/PDI.
    151    // See bug 1439018 for details.
    152    let default_use_isolating = false;
    153    let use_isolating =
    154        get_bool_pref(cstr!("intl.l10n.enable-bidi-marks")).unwrap_or(default_use_isolating);
    155    bundle.set_use_isolating(use_isolating);
    156 }
    157 
    158 #[no_mangle]
    159 pub extern "C" fn fluent_bundle_new_single(
    160    locale: &nsACString,
    161    use_isolating: bool,
    162    pseudo_strategy: &nsACString,
    163 ) -> *mut FluentBundleRc {
    164    let id = match locale.to_utf8().parse::<LanguageIdentifier>() {
    165        Ok(id) => id,
    166        Err(..) => return std::ptr::null_mut(),
    167    };
    168 
    169    Box::into_raw(fluent_bundle_new_internal(
    170        &[id],
    171        use_isolating,
    172        pseudo_strategy,
    173    ))
    174 }
    175 
    176 #[no_mangle]
    177 pub unsafe extern "C" fn fluent_bundle_new(
    178    locales: *const nsCString,
    179    locale_count: usize,
    180    use_isolating: bool,
    181    pseudo_strategy: &nsACString,
    182 ) -> *mut FluentBundleRc {
    183    let mut langids = Vec::with_capacity(locale_count);
    184    let locales = std::slice::from_raw_parts(locales, locale_count);
    185    for locale in locales {
    186        let id = match locale.to_utf8().parse::<LanguageIdentifier>() {
    187            Ok(id) => id,
    188            Err(..) => return std::ptr::null_mut(),
    189        };
    190        langids.push(id);
    191    }
    192 
    193    Box::into_raw(fluent_bundle_new_internal(
    194        &langids,
    195        use_isolating,
    196        pseudo_strategy,
    197    ))
    198 }
    199 
    200 fn fluent_bundle_new_internal(
    201    langids: &[LanguageIdentifier],
    202    use_isolating: bool,
    203    pseudo_strategy: &nsACString,
    204 ) -> Box<FluentBundleRc> {
    205    let mut bundle = FluentBundle::new(langids.to_vec());
    206    bundle.set_use_isolating(use_isolating);
    207 
    208    bundle.set_formatter(Some(format_numbers));
    209 
    210    adapt_bundle_for_gecko(&mut bundle, Some(pseudo_strategy));
    211 
    212    Box::new(bundle)
    213 }
    214 
    215 #[no_mangle]
    216 pub extern "C" fn fluent_bundle_get_locales(
    217    bundle: &FluentBundleRc,
    218    result: &mut ThinVec<nsCString>,
    219 ) {
    220    for locale in &bundle.locales {
    221        result.push(locale.to_string().as_str().into());
    222    }
    223 }
    224 
    225 #[no_mangle]
    226 pub unsafe extern "C" fn fluent_bundle_destroy(bundle: *mut FluentBundleRc) {
    227    let _ = Box::from_raw(bundle);
    228 }
    229 
    230 #[no_mangle]
    231 pub extern "C" fn fluent_bundle_has_message(bundle: &FluentBundleRc, id: &nsACString) -> bool {
    232    bundle.has_message(id.to_string().as_str())
    233 }
    234 
    235 #[no_mangle]
    236 pub extern "C" fn fluent_bundle_get_message(
    237    bundle: &FluentBundleRc,
    238    id: &nsACString,
    239    has_value: &mut bool,
    240    attrs: &mut ThinVec<nsCString>,
    241 ) -> bool {
    242    match bundle.get_message(&id.to_utf8()) {
    243        Some(message) => {
    244            attrs.reserve(message.attributes().count());
    245            *has_value = message.value().is_some();
    246            for attr in message.attributes() {
    247                attrs.push(attr.id().into());
    248            }
    249            true
    250        }
    251        None => {
    252            *has_value = false;
    253            false
    254        }
    255    }
    256 }
    257 
    258 #[no_mangle]
    259 pub extern "C" fn fluent_bundle_format_pattern(
    260    bundle: &FluentBundleRc,
    261    id: &nsACString,
    262    attr: &nsACString,
    263    args: &ThinVec<L10nArg>,
    264    ret_val: &mut nsACString,
    265    ret_errors: &mut ThinVec<nsCString>,
    266 ) -> bool {
    267    let args = convert_args(&args);
    268 
    269    let message = match bundle.get_message(&id.to_utf8()) {
    270        Some(message) => message,
    271        None => return false,
    272    };
    273 
    274    let pattern = if !attr.is_empty() {
    275        match message.get_attribute(&attr.to_utf8()) {
    276            Some(attr) => attr.value(),
    277            None => return false,
    278        }
    279    } else {
    280        match message.value() {
    281            Some(value) => value,
    282            None => return false,
    283        }
    284    };
    285 
    286    let mut errors = vec![];
    287    bundle
    288        .write_pattern(ret_val, pattern, args.as_ref(), &mut errors)
    289        .expect("Failed to write to a nsCString.");
    290    append_fluent_errors_to_ret_errors(ret_errors, &errors);
    291    true
    292 }
    293 
    294 #[no_mangle]
    295 pub unsafe extern "C" fn fluent_bundle_add_resource(
    296    bundle: &mut FluentBundleRc,
    297    r: *const FluentResource,
    298    allow_overrides: bool,
    299    ret_errors: &mut ThinVec<nsCString>,
    300 ) {
    301    // we don't own the resource
    302    let r = mem::ManuallyDrop::new(Rc::from_raw(r));
    303 
    304    if allow_overrides {
    305        bundle.add_resource_overriding(Rc::clone(&r));
    306    } else if let Err(errors) = bundle.add_resource(Rc::clone(&r)) {
    307        append_fluent_errors_to_ret_errors(ret_errors, &errors);
    308    }
    309 }
    310 
    311 pub fn convert_args<'s>(args: &[L10nArg<'s>]) -> Option<FluentArgs<'s>> {
    312    if args.is_empty() {
    313        return None;
    314    }
    315 
    316    let mut result = FluentArgs::with_capacity(args.len());
    317    for arg in args {
    318        let val = match arg.value {
    319            FluentArgument::Double_(d) => FluentValue::from(d),
    320            FluentArgument::String(s) => FluentValue::from(s.to_utf8()),
    321        };
    322        result.set(arg.id.to_string(), val);
    323    }
    324    Some(result)
    325 }
    326 
    327 fn append_fluent_errors_to_ret_errors(ret_errors: &mut ThinVec<nsCString>, errors: &[FluentError]) {
    328    for error in errors {
    329        ret_errors.push(error.to_string().into());
    330    }
    331 }