From 05db766a87126fb998558be4d5ade73b3754bf64 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki Date: Tue, 3 Aug 2021 16:25:10 +0000 Subject: [PATCH] Bug 1613705 - [localization] part1: Introduce localization-ffi bindings for fluent-fallback. r=emilio,nika Depends on D117349 Differential Revision: https://phabricator.services.mozilla.com/D104788 --- Cargo.lock | 23 + intl/l10n/FluentBundle.cpp | 40 +- intl/l10n/LocalizationBindings.h | 26 + intl/l10n/moz.build | 7 + intl/l10n/rust/fluent-ffi/src/bundle.rs | 55 +- intl/l10n/rust/l10nregistry-ffi/src/env.rs | 8 + intl/l10n/rust/l10nregistry-ffi/src/lib.rs | 4 +- intl/l10n/rust/localization-ffi/Cargo.toml | 23 + intl/l10n/rust/localization-ffi/cbindgen.toml | 25 + intl/l10n/rust/localization-ffi/src/lib.rs | 508 ++++++++++++++++++ toolkit/library/rust/shared/Cargo.toml | 1 + toolkit/library/rust/shared/lib.rs | 4 +- 12 files changed, 674 insertions(+), 50 deletions(-) create mode 100644 intl/l10n/LocalizationBindings.h create mode 100644 intl/l10n/rust/localization-ffi/Cargo.toml create mode 100644 intl/l10n/rust/localization-ffi/cbindgen.toml create mode 100644 intl/l10n/rust/localization-ffi/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 166f1233fd07..9c15f54b2b57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2013,6 +2013,7 @@ dependencies = [ "l10nregistry", "l10nregistry-ffi", "lmdb-rkv-sys", + "localization-ffi", "log", "mapped_hyph", "mdns_service", @@ -2809,6 +2810,28 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "localization-ffi" +version = "0.1.0" +dependencies = [ + "async-trait", + "cstr", + "fluent", + "fluent-fallback", + "fluent-ffi", + "futures 0.3.15", + "futures-channel", + "l10nregistry", + "l10nregistry-ffi", + "libc", + "moz_task", + "nserror", + "nsstring", + "thin-vec", + "unic-langid", + "xpcom", +] + [[package]] name = "lock_api" version = "0.4.4" diff --git a/intl/l10n/FluentBundle.cpp b/intl/l10n/FluentBundle.cpp index fa6dc8e4b515..4ba4fd72be7b 100644 --- a/intl/l10n/FluentBundle.cpp +++ b/intl/l10n/FluentBundle.cpp @@ -188,35 +188,39 @@ bool extendJSArrayWithErrors(JSContext* aCx, JS::Handle aErrors, return true; } +static void ConvertArgs(const L10nArgs& aArgs, + nsTArray& aRetVal) { + for (const auto& entry : aArgs.Entries()) { + if (!entry.mValue.IsNull()) { + const auto& value = entry.mValue.Value(); + + if (value.IsUTF8String()) { + aRetVal.AppendElement(ffi::L10nArg{ + &entry.mKey, + ffi::FluentArgument::String(&value.GetAsUTF8String())}); + } else { + aRetVal.AppendElement(ffi::L10nArg{ + &entry.mKey, ffi::FluentArgument::Double_(value.GetAsDouble())}); + } + } + } +} + void FluentBundle::FormatPattern(JSContext* aCx, const FluentPattern& aPattern, const Nullable& aArgs, const Optional>& aErrors, nsACString& aRetVal, ErrorResult& aRv) { - nsTArray argIds; - nsTArray argValues; + nsTArray l10nArgs; if (!aArgs.IsNull()) { const L10nArgs& args = aArgs.Value(); - for (auto& entry : args.Entries()) { - if (!entry.mValue.IsNull()) { - argIds.AppendElement(entry.mKey); - - auto& value = entry.mValue.Value(); - if (value.IsUTF8String()) { - argValues.AppendElement( - ffi::FluentArgument::String(&value.GetAsUTF8String())); - } else { - argValues.AppendElement( - ffi::FluentArgument::Double_(value.GetAsDouble())); - } - } - } + ConvertArgs(args, l10nArgs); } nsTArray errors; bool succeeded = fluent_bundle_format_pattern(mRaw.get(), &aPattern.mId, - &aPattern.mAttrName, &argIds, - &argValues, &aRetVal, &errors); + &aPattern.mAttrName, &l10nArgs, + &aRetVal, &errors); if (!succeeded) { return aRv.ThrowInvalidStateError( diff --git a/intl/l10n/LocalizationBindings.h b/intl/l10n/LocalizationBindings.h new file mode 100644 index 000000000000..fbf4db2db0c9 --- /dev/null +++ b/intl/l10n/LocalizationBindings.h @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_intl_l10n_LocalizationBindings_h +#define mozilla_intl_l10n_LocalizationBindings_h + +#include "mozilla/intl/localization_ffi_generated.h" + +#include "mozilla/RefPtr.h" + +namespace mozilla { + +template <> +struct RefPtrTraits { + static void AddRef(const intl::ffi::LocalizationRc* aPtr) { + intl::ffi::localization_addref(aPtr); + } + static void Release(const intl::ffi::LocalizationRc* aPtr) { + intl::ffi::localization_release(aPtr); + } +}; + +} // namespace mozilla + +#endif diff --git a/intl/l10n/moz.build b/intl/l10n/moz.build index cb985d51b0ea..4583838ab7d7 100644 --- a/intl/l10n/moz.build +++ b/intl/l10n/moz.build @@ -11,6 +11,7 @@ EXPORTS.mozilla.intl += [ "FluentResource.h", "L10nRegistry.h", "Localization.h", + "LocalizationBindings.h", "RegistryBindings.h", ] @@ -46,13 +47,19 @@ USE_LIBS += ["intlcomponents"] if CONFIG["COMPILE_ENVIRONMENT"]: CbindgenHeader("fluent_ffi_generated.h", inputs=["/intl/l10n/rust/fluent-ffi"]) + CbindgenHeader( "l10nregistry_ffi_generated.h", inputs=["/intl/l10n/rust/l10nregistry-ffi"] ) + CbindgenHeader( + "localization_ffi_generated.h", inputs=["/intl/l10n/rust/localization-ffi"] + ) + EXPORTS.mozilla.intl += [ "!fluent_ffi_generated.h", "!l10nregistry_ffi_generated.h", + "!localization_ffi_generated.h", ] XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell.ini"] diff --git a/intl/l10n/rust/fluent-ffi/src/bundle.rs b/intl/l10n/rust/fluent-ffi/src/bundle.rs index a8b282688fd7..2f8385746335 100644 --- a/intl/l10n/rust/fluent-ffi/src/bundle.rs +++ b/intl/l10n/rust/fluent-ffi/src/bundle.rs @@ -20,9 +20,16 @@ pub type FluentBundleRc = FluentBundle>; #[derive(Debug)] #[repr(C, u8)] -pub enum FluentArgument { +pub enum FluentArgument<'s> { Double_(f64), - String(*const nsCString), + String(&'s nsACString), +} + +#[derive(Debug)] +#[repr(C)] +pub struct L10nArg<'s> { + pub id: &'s nsACString, + pub value: FluentArgument<'s>, } fn transform_accented(s: &str) -> Cow { @@ -149,14 +156,12 @@ pub fn adapt_bundle_for_gecko(bundle: &mut FluentBundleRc, pseudo_strategy: Opti } #[no_mangle] -pub unsafe extern "C" fn fluent_bundle_new_single( +pub extern "C" fn fluent_bundle_new_single( locale: &nsACString, use_isolating: bool, pseudo_strategy: &nsACString, ) -> *mut FluentBundleRc { - // We can use as_str_unchecked because this string comes from WebIDL and is - // guaranteed utf-8. - let id = match locale.as_str_unchecked().parse::() { + let id = match locale.to_utf8().parse::() { Ok(id) => id, Err(..) => return std::ptr::null_mut(), }; @@ -178,7 +183,7 @@ pub unsafe extern "C" fn fluent_bundle_new( let mut langids = Vec::with_capacity(locale_count); let locales = std::slice::from_raw_parts(locales, locale_count); for locale in locales { - let id = match locale.as_str_unchecked().parse::() { + let id = match locale.to_utf8().parse::() { Ok(id) => id, Err(..) => return std::ptr::null_mut(), }; @@ -228,13 +233,13 @@ pub extern "C" fn fluent_bundle_has_message(bundle: &FluentBundleRc, id: &nsACSt } #[no_mangle] -pub unsafe extern "C" fn fluent_bundle_get_message( +pub extern "C" fn fluent_bundle_get_message( bundle: &FluentBundleRc, id: &nsACString, has_value: &mut bool, attrs: &mut ThinVec, ) -> bool { - match bundle.get_message(id.as_str_unchecked()) { + match bundle.get_message(&id.to_utf8()) { Some(message) => { attrs.reserve(message.attributes().count()); *has_value = message.value().is_some(); @@ -251,24 +256,23 @@ pub unsafe extern "C" fn fluent_bundle_get_message( } #[no_mangle] -pub unsafe extern "C" fn fluent_bundle_format_pattern( +pub extern "C" fn fluent_bundle_format_pattern( bundle: &FluentBundleRc, id: &nsACString, attr: &nsACString, - arg_ids: &ThinVec, - arg_vals: &ThinVec, + args: &ThinVec, ret_val: &mut nsACString, ret_errors: &mut ThinVec, ) -> bool { - let args = convert_args(arg_ids, arg_vals); + let args = convert_args(&args); - let message = match bundle.get_message(id.as_str_unchecked()) { + let message = match bundle.get_message(&id.to_utf8()) { Some(message) => message, None => return false, }; let pattern = if !attr.is_empty() { - match message.get_attribute(attr.as_str_unchecked()) { + match message.get_attribute(&attr.to_utf8()) { Some(attr) => attr.value(), None => return false, } @@ -304,25 +308,20 @@ pub unsafe extern "C" fn fluent_bundle_add_resource( } } -fn convert_args<'a>( - arg_ids: &'a [nsCString], - arg_vals: &'a [FluentArgument], -) -> Option> { - debug_assert_eq!(arg_ids.len(), arg_vals.len()); - - if arg_ids.is_empty() { +pub fn convert_args<'s>(args: &[L10nArg<'s>]) -> Option> { + if args.is_empty() { return None; } - let mut args = FluentArgs::with_capacity(arg_ids.len()); - for (id, val) in arg_ids.iter().zip(arg_vals.iter()) { - let val = match val { + let mut result = FluentArgs::with_capacity(args.len()); + for arg in args { + let val = match arg.value { FluentArgument::Double_(d) => FluentValue::from(d), - FluentArgument::String(s) => FluentValue::from(unsafe { (**s).to_string() }), + FluentArgument::String(s) => FluentValue::from(s.to_utf8()), }; - args.set(id.to_string(), val); + result.set(arg.id.to_string(), val); } - Some(args) + Some(result) } fn append_fluent_errors_to_ret_errors(ret_errors: &mut ThinVec, errors: &[FluentError]) { diff --git a/intl/l10n/rust/l10nregistry-ffi/src/env.rs b/intl/l10n/rust/l10nregistry-ffi/src/env.rs index cfb9aad9a675..f4f312f8e789 100644 --- a/intl/l10n/rust/l10nregistry-ffi/src/env.rs +++ b/intl/l10n/rust/l10nregistry-ffi/src/env.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cstr::cstr; +use fluent_fallback::env::LocalesProvider; use l10nregistry::{ env::ErrorReporter, errors::{L10nRegistryError, L10nRegistrySetupError}, @@ -68,6 +69,13 @@ impl ErrorReporter for GeckoEnvironment { } } +impl LocalesProvider for GeckoEnvironment { + type Iter = std::vec::IntoIter; + fn locales(&self) -> Self::Iter { + vec!["en-US".parse().unwrap()].into_iter() + } +} + fn log_simple_console_error( error: &impl fmt::Display, category: &CStr, diff --git a/intl/l10n/rust/l10nregistry-ffi/src/lib.rs b/intl/l10n/rust/l10nregistry-ffi/src/lib.rs index 201938b6ce38..843860abf922 100644 --- a/intl/l10n/rust/l10nregistry-ffi/src/lib.rs +++ b/intl/l10n/rust/l10nregistry-ffi/src/lib.rs @@ -2,9 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -mod env; +pub mod env; mod fetcher; pub mod load; -mod registry; +pub mod registry; mod source; mod xpcom_utils; diff --git a/intl/l10n/rust/localization-ffi/Cargo.toml b/intl/l10n/rust/localization-ffi/Cargo.toml new file mode 100644 index 000000000000..a32aa31d4220 --- /dev/null +++ b/intl/l10n/rust/localization-ffi/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "localization-ffi" +version = "0.1.0" +authors = ["nobody@mozilla.org"] +edition = "2018" + +[dependencies] +futures-channel = "0.3" +futures = "0.3" +libc = "0.2" +nserror = { path = "../../../../xpcom/rust/nserror" } +nsstring = { path = "../../../../xpcom/rust/nsstring" } +l10nregistry = { git = "https://github.com/mozilla/l10nregistry-rs", rev = "55bf7f826d773303a67d8d7fdab099a04322d4fb" } +fluent = { version = "0.16", features = ["fluent-pseudo"] } +unic-langid = "0.9" +thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } +async-trait = "0.1" +moz_task = { path = "../../../../xpcom/rust/moz_task" } +fluent-ffi = { path = "../fluent-ffi" } +fluent-fallback = "0.5" +l10nregistry-ffi = { path = "../l10nregistry-ffi" } +xpcom = { path = "../../../../xpcom/rust/xpcom" } +cstr = "0.2" diff --git a/intl/l10n/rust/localization-ffi/cbindgen.toml b/intl/l10n/rust/localization-ffi/cbindgen.toml new file mode 100644 index 000000000000..114d4f36cb71 --- /dev/null +++ b/intl/l10n/rust/localization-ffi/cbindgen.toml @@ -0,0 +1,25 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ +#ifndef mozilla_intl_l10n_LocalizationBindings_h +#error "Don't include this file directly, instead include LocalizationBindings.h" +#endif +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +namespaces = ["mozilla", "intl", "ffi"] + +[parse] +parse_deps = true +include = ["fluent-fallback"] + +[enum] +derive_helper_methods = true + +[export.rename] +"ThinVec" = "nsTArray" +"Promise" = "dom::Promise" diff --git a/intl/l10n/rust/localization-ffi/src/lib.rs b/intl/l10n/rust/localization-ffi/src/lib.rs new file mode 100644 index 000000000000..22f1389fe4b1 --- /dev/null +++ b/intl/l10n/rust/localization-ffi/src/lib.rs @@ -0,0 +1,508 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use fluent::FluentValue; +use fluent_fallback::{ + types::{ + L10nAttribute as FluentL10nAttribute, L10nKey as FluentL10nKey, + L10nMessage as FluentL10nMessage, + }, + Localization, +}; +use fluent_ffi::{convert_args, FluentArgs, FluentArgument, L10nArg}; +use l10nregistry_ffi::{ + env::GeckoEnvironment, + registry::{get_l10n_registry, GeckoL10nRegistry}, +}; +use nserror::{nsresult, NS_OK}; +use nsstring::{nsACString, nsCString}; +use std::{borrow::Cow, cell::RefCell}; +use thin_vec::ThinVec; +use xpcom::{interfaces::nsrefcnt, RefCounted, RefPtr, Refcnt}; + +#[derive(Debug)] +#[repr(C)] +pub struct L10nKey<'s> { + id: &'s nsACString, + args: ThinVec>, +} + +impl<'s> From<&'s L10nKey<'s>> for FluentL10nKey<'static> { + fn from(input: &'s L10nKey<'s>) -> Self { + FluentL10nKey { + id: input.id.to_utf8().to_string().into(), + args: convert_args_to_owned(&input.args), + } + } +} + +// This is a variant of `convert_args` from `fluent-ffi` with a 'static constrain +// put on the resulting `FluentArgs` to make it acceptable into `spqwn_current_thread`. +pub fn convert_args_to_owned(args: &[L10nArg]) -> Option> { + if args.is_empty() { + return None; + } + + let mut result = FluentArgs::with_capacity(args.len()); + for arg in args { + let val = match arg.value { + FluentArgument::Double_(d) => FluentValue::from(d), + // We need this to be owned because we pass the result into `spawn_current_thread`. + FluentArgument::String(s) => FluentValue::from(Cow::Owned(s.to_utf8().to_string())), + }; + result.set(arg.id.to_string(), val); + } + Some(result) +} + +#[derive(Debug)] +#[repr(C)] +pub struct L10nAttribute { + name: nsCString, + value: nsCString, +} + +impl From> for L10nAttribute { + fn from(attr: FluentL10nAttribute<'_>) -> Self { + Self { + name: nsCString::from(&*attr.name), + value: nsCString::from(&*attr.value), + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct L10nMessage { + value: nsCString, + attributes: ThinVec, +} + +impl std::default::Default for L10nMessage { + fn default() -> Self { + Self { + value: nsCString::new(), + attributes: ThinVec::new(), + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct OptionalL10nMessage { + is_present: bool, + message: L10nMessage, +} + +impl From> for L10nMessage { + fn from(input: FluentL10nMessage) -> Self { + let value = if let Some(value) = input.value { + value.to_string().into() + } else { + let mut s = nsCString::new(); + s.set_is_void(true); + s + }; + Self { + value, + attributes: input.attributes.into_iter().map(Into::into).collect(), + } + } +} + +pub struct LocalizationRc { + inner: RefCell>, + refcnt: Refcnt, +} + +// xpcom::RefPtr support +unsafe impl RefCounted for LocalizationRc { + unsafe fn addref(&self) { + localization_addref(self); + } + unsafe fn release(&self) { + localization_release(self); + } +} + +impl LocalizationRc { + pub fn new(reg: &GeckoL10nRegistry, res_ids: Vec, is_sync: bool) -> RefPtr { + let inner = Localization::with_env(res_ids, is_sync, GeckoEnvironment, reg.clone()); + + let loc = Box::new(LocalizationRc { + inner: RefCell::new(inner), + refcnt: unsafe { Refcnt::new() }, + }); + + unsafe { + RefPtr::from_raw(Box::into_raw(loc)) + .expect("Failed to create RefPtr from Box") + } + } + + pub fn add_resource_id(&self, res_id: String) { + self.inner.borrow_mut().add_resource_id(res_id); + } + + pub fn add_resource_ids(&self, res_ids: Vec) { + self.inner.borrow_mut().add_resource_ids(res_ids); + } + + pub fn remove_resource_id(&self, res_id: String) -> usize { + self.inner.borrow_mut().remove_resource_id(res_id) + } + + pub fn remove_resource_ids(&self, res_ids: Vec) -> usize { + self.inner.borrow_mut().remove_resource_ids(res_ids) + } + + /// Upgrade synchronous mode to asynchronous. + pub fn upgrade(&self) { + if self.is_sync() { + self.inner.borrow_mut().set_async(); + } + } + + pub fn is_sync(&self) -> bool { + self.inner.borrow().is_sync() + } + + pub fn format_value_sync( + &self, + id: &nsACString, + args: &ThinVec, + ret_val: &mut nsACString, + ret_err: &mut ThinVec, + ) -> bool { + let mut errors = vec![]; + let args = convert_args(&args); + if let Ok(value) = self.inner.borrow().bundles().format_value_sync( + &id.to_utf8(), + args.as_ref(), + &mut errors, + ) { + if let Some(value) = value { + ret_val.assign(&value); + } else { + ret_val.set_is_void(true); + } + ret_err.extend(errors.into_iter().map(|err| err.to_string().into())); + true + } else { + false + } + } + + pub fn format_values_sync( + &self, + keys: &ThinVec, + ret_val: &mut ThinVec, + ret_err: &mut ThinVec, + ) -> bool { + ret_val.reserve(keys.len()); + let keys: Vec = keys.into_iter().map(|k| k.into()).collect(); + let mut errors = vec![]; + if let Ok(values) = self + .inner + .borrow() + .bundles() + .format_values_sync(&keys, &mut errors) + { + for value in values.iter() { + if let Some(value) = value { + ret_val.push(value.as_ref().into()); + } else { + let mut void_string = nsCString::new(); + void_string.set_is_void(true); + ret_val.push(void_string); + } + } + ret_err.extend(errors.into_iter().map(|err| err.to_string().into())); + true + } else { + false + } + } + + pub fn format_messages_sync( + &self, + keys: &ThinVec, + ret_val: &mut ThinVec, + ret_err: &mut ThinVec, + ) -> bool { + ret_val.reserve(keys.len()); + let mut errors = vec![]; + let keys: Vec = keys.into_iter().map(|k| k.into()).collect(); + if let Ok(messages) = self + .inner + .borrow() + .bundles() + .format_messages_sync(&keys, &mut errors) + { + for msg in messages { + ret_val.push(if let Some(msg) = msg { + OptionalL10nMessage { + is_present: true, + message: msg.into(), + } + } else { + OptionalL10nMessage { + is_present: false, + message: L10nMessage::default(), + } + }); + } + assert_eq!(keys.len(), ret_val.len()); + ret_err.extend(errors.into_iter().map(|err| err.to_string().into())); + true + } else { + false + } + } + + pub fn format_value( + &self, + id: &nsACString, + args: &ThinVec, + promise: &xpcom::Promise, + callback: extern "C" fn(&xpcom::Promise, &nsACString, &ThinVec), + ) { + let bundles = self.inner.borrow().bundles().clone(); + + let args = convert_args_to_owned(&args); + + let id = nsCString::from(id); + let strong_promise = RefPtr::new(promise); + + moz_task::spawn_current_thread(async move { + let mut errors = vec![]; + let value = if let Some(value) = bundles + .format_value(&id.to_utf8(), args.as_ref(), &mut errors) + .await + { + let v: nsCString = value.to_string().into(); + v + } else { + let mut v = nsCString::new(); + v.set_is_void(true); + v + }; + let errors = errors + .into_iter() + .map(|err| err.to_string().into()) + .collect(); + callback(&strong_promise, &value, &errors); + }) + .expect("Failed to spawn future"); + } + + pub fn format_values( + &self, + keys: &ThinVec, + promise: &xpcom::Promise, + callback: extern "C" fn(&xpcom::Promise, Option<&ThinVec>), + ) { + let bundles = self.inner.borrow().bundles().clone(); + + let keys: Vec = keys.into_iter().map(|k| k.into()).collect(); + + let strong_promise = RefPtr::new(promise); + + moz_task::spawn_current_thread(async move { + let mut errors = vec![]; + let ret_val = bundles + .format_values(&keys, &mut errors) + .await + .into_iter() + .map(|value| { + if let Some(value) = value { + nsCString::from(value.as_ref()) + } else { + let mut v = nsCString::new(); + v.set_is_void(true); + v + } + }) + .collect::>(); + + callback(&strong_promise, Some(&ret_val)); + }) + .expect("Failed to spawn future"); + } + + pub fn format_messages( + &self, + keys: &ThinVec, + promise: &xpcom::Promise, + callback: extern "C" fn( + &xpcom::Promise, + Option<&ThinVec>, + &ThinVec, + ), + ) { + let bundles = self.inner.borrow().bundles().clone(); + + let keys: Vec = keys.into_iter().map(|k| k.into()).collect(); + + let strong_promise = RefPtr::new(promise); + + moz_task::spawn_current_thread(async move { + let mut errors = vec![]; + let ret_val = bundles + .format_messages(&keys, &mut errors) + .await + .into_iter() + .map(|msg| { + if let Some(msg) = msg { + OptionalL10nMessage { + is_present: true, + message: msg.into(), + } + } else { + OptionalL10nMessage { + is_present: false, + message: L10nMessage::default(), + } + } + }) + .collect::>(); + + assert_eq!(keys.len(), ret_val.len()); + + let errors = errors + .into_iter() + .map(|err| err.to_string().into()) + .collect(); + + callback(&strong_promise, Some(&ret_val), &errors); + }) + .expect("Failed to spawn future"); + } +} + +#[no_mangle] +pub extern "C" fn localization_new( + res_ids: &ThinVec, + is_sync: bool, + result: &mut *const LocalizationRc, +) -> nsresult { + *result = std::ptr::null(); + + let reg = get_l10n_registry(); + let res_ids: Vec = res_ids.iter().map(|res| res.to_string()).collect(); + *result = RefPtr::forget_into_raw(LocalizationRc::new(®, res_ids, is_sync)); + NS_OK +} + +#[no_mangle] +pub unsafe extern "C" fn localization_addref(loc: &LocalizationRc) -> nsrefcnt { + loc.refcnt.inc() +} + +#[no_mangle] +pub unsafe extern "C" fn localization_release(loc: *const LocalizationRc) -> nsrefcnt { + let rc = (*loc).refcnt.dec(); + if rc == 0 { + Box::from_raw(loc as *const _ as *mut LocalizationRc); + } + rc +} + +#[no_mangle] +pub extern "C" fn localization_add_res_id(loc: &LocalizationRc, res_id: &nsACString) { + let res_id = res_id.to_string(); + loc.add_resource_id(res_id); +} + +#[no_mangle] +pub extern "C" fn localization_add_res_ids(loc: &LocalizationRc, res_ids: &ThinVec) { + let res_ids = res_ids.iter().map(|s| s.to_string()).collect(); + loc.add_resource_ids(res_ids); +} + +#[no_mangle] +pub extern "C" fn localization_remove_res_id(loc: &LocalizationRc, res_id: &nsACString) -> usize { + let res_id = res_id.to_string(); + loc.remove_resource_id(res_id) +} + +#[no_mangle] +pub extern "C" fn localization_remove_res_ids( + loc: &LocalizationRc, + res_ids: &ThinVec, +) -> usize { + let res_ids = res_ids.iter().map(|s| s.to_string()).collect(); + loc.remove_resource_ids(res_ids) +} + +#[no_mangle] +pub extern "C" fn localization_format_value_sync( + loc: &LocalizationRc, + id: &nsACString, + args: &ThinVec, + ret_val: &mut nsACString, + ret_err: &mut ThinVec, +) -> bool { + loc.format_value_sync(id, args, ret_val, ret_err) +} + +#[no_mangle] +pub extern "C" fn localization_format_values_sync( + loc: &LocalizationRc, + keys: &ThinVec, + ret_val: &mut ThinVec, + ret_err: &mut ThinVec, +) -> bool { + loc.format_values_sync(keys, ret_val, ret_err) +} + +#[no_mangle] +pub extern "C" fn localization_format_messages_sync( + loc: &LocalizationRc, + keys: &ThinVec, + ret_val: &mut ThinVec, + ret_err: &mut ThinVec, +) -> bool { + loc.format_messages_sync(keys, ret_val, ret_err) +} + +#[no_mangle] +pub extern "C" fn localization_format_value( + loc: &LocalizationRc, + id: &nsACString, + args: &ThinVec, + promise: &xpcom::Promise, + callback: extern "C" fn(&xpcom::Promise, &nsACString, &ThinVec), +) { + loc.format_value(id, args, promise, callback); +} + +#[no_mangle] +pub extern "C" fn localization_format_values( + loc: &LocalizationRc, + keys: &ThinVec, + promise: &xpcom::Promise, + callback: extern "C" fn(&xpcom::Promise, Option<&ThinVec>), +) { + loc.format_values(keys, promise, callback); +} + +#[no_mangle] +pub extern "C" fn localization_format_messages( + loc: &LocalizationRc, + keys: &ThinVec, + promise: &xpcom::Promise, + callback: extern "C" fn( + &xpcom::Promise, + Option<&ThinVec>, + &ThinVec, + ), +) { + loc.format_messages(keys, promise, callback); +} + +#[no_mangle] +pub extern "C" fn localization_upgrade(loc: &LocalizationRc) { + loc.upgrade(); +} diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index 9eeae609ea63..da0c060c412a 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -67,6 +67,7 @@ fluent-ffi = { path = "../../../../intl/l10n/rust/fluent-ffi" } l10nregistry-ffi = { path = "../../../../intl/l10n/rust/l10nregistry-ffi" } l10nregistry = { git = "https://github.com/mozilla/l10nregistry-rs", rev = "55bf7f826d773303a67d8d7fdab099a04322d4fb" } fluent-fallback = "0.5" +localization-ffi = { path = "../../../../intl/l10n/rust/localization-ffi" } processtools = { path = "../../../components/processtools" } qcms = { path = "../../../../gfx/qcms", features = ["c_bindings", "neon"], default-features = false } diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index a038218b78aa..38b92b16c7fb 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -29,7 +29,6 @@ extern crate gkrust_utils; extern crate http_sfv; extern crate jsrust_shared; extern crate kvstore; -extern crate l10nregistry_ffi; extern crate mapped_hyph; extern crate mozurl; extern crate mp4parse_capi; @@ -75,7 +74,8 @@ extern crate fluent; extern crate fluent_ffi; extern crate fluent_fallback; -extern crate l10nregistry; +extern crate l10nregistry_ffi; +extern crate localization_ffi; #[cfg(not(target_os = "android"))] extern crate viaduct;