Bug 1660393 - P3: Implement load_sync and load_async functions for L10n. r=zbraniecki,froydnj

Differential Revision: https://phabricator.services.mozilla.com/D89695
This commit is contained in:
Dan Glastonbury 2020-10-21 05:48:10 +00:00
parent 0b93a75baa
commit f5c6d9e6a5
13 changed files with 385 additions and 0 deletions

20
Cargo.lock generated
View File

@ -1950,6 +1950,7 @@ dependencies = [
"fog-gtest",
"gecko-fuzz-targets",
"gkrust-shared",
"l10nregistry-ffi-gtest",
"moz_task-gtest",
"mozglue-static",
"mp4parse-gtest",
@ -1989,6 +1990,7 @@ dependencies = [
"http_sfv",
"jsrust_shared",
"kvstore",
"l10nregistry-ffi",
"lmdb-rkv-sys",
"log",
"mapped_hyph",
@ -2606,6 +2608,24 @@ dependencies = [
"xpcom",
]
[[package]]
name = "l10nregistry-ffi"
version = "0.1.0"
dependencies = [
"futures-channel",
"libc",
"nserror",
"nsstring",
]
[[package]]
name = "l10nregistry-ffi-gtest"
version = "0.1.0"
dependencies = [
"l10nregistry-ffi",
"moz_task",
]
[[package]]
name = "lazy_static"
version = "1.4.0"

134
intl/l10n/L10nRegistry.cpp Normal file
View File

@ -0,0 +1,134 @@
/* 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/. */
#include "mozilla/RefPtr.h"
#include "mozilla/URLPreloader.h"
#include "nsIChannel.h"
#include "nsILoadInfo.h"
#include "nsIStreamLoader.h"
#include "nsString.h"
namespace mozilla {
namespace intl {
class ResourceLoader final : public nsIStreamLoaderObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
typedef nsresult (*Callback)(void* aClosure, nsACString* aString,
nsresult aSuccess);
ResourceLoader(Callback aCallback, void* aClosure)
: mCallback(aCallback), mClosure(aClosure) {}
protected:
~ResourceLoader() = default;
private:
Callback mCallback;
void* mClosure;
};
NS_IMPL_ISUPPORTS(ResourceLoader, nsIStreamLoaderObserver)
// nsIStreamLoaderObserver
NS_IMETHODIMP
ResourceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
nsISupports* aContext, nsresult aStatus,
uint32_t aStringLen, const uint8_t* aString) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(aStatus)) {
mCallback(mClosure, nullptr, aStatus);
return NS_OK;
}
nsCString data;
data.Adopt(reinterpret_cast<char*>(const_cast<uint8_t*>(aString)),
aStringLen);
mCallback(mClosure, &data, NS_OK);
return NS_SUCCESS_ADOPTED_DATA;
}
class L10nRegistry {
public:
static nsresult Load(const nsACString& aPath,
ResourceLoader::Callback aCallback, void* aClosure) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG);
RefPtr<ResourceLoader> listener =
MakeRefPtr<ResourceLoader>(aCallback, aClosure);
// TODO: What is the lifetime requirement for loader?
RefPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(
getter_AddRefs(loader), uri, listener,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_OTHER);
return rv;
}
static nsresult LoadSync(const nsACString& aPath, nsACString& aRetVal) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG);
auto result = URLPreloader::ReadURI(uri);
if (result.isOk()) {
aRetVal = result.unwrap();
return NS_OK;
}
auto err = result.unwrapErr();
if (err != NS_ERROR_INVALID_ARG && err != NS_ERROR_NOT_INITIALIZED) {
return err;
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(
getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
nullptr, /* aPerformanceStorage */
nullptr, /* aLoadGroup */
nullptr, /* aCallbacks */
nsIRequest::LOAD_BACKGROUND);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> input;
rv = channel->Open(getter_AddRefs(input));
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
return NS_ReadInputStreamToString(input, aRetVal, -1);
}
};
} // namespace intl
} // namespace mozilla
extern "C" {
nsresult L10nRegistryLoad(const nsACString* aPath,
mozilla::intl::ResourceLoader::Callback aCallback,
void* aClosure) {
if (!aPath || !aCallback) {
return NS_ERROR_INVALID_ARG;
}
return mozilla::intl::L10nRegistry::Load(*aPath, aCallback, aClosure);
}
nsresult L10nRegistryLoadSync(const nsACString* aPath, nsACString* aRetVal) {
return mozilla::intl::L10nRegistry::LoadSync(*aPath, *aRetVal);
}
}

View File

@ -14,6 +14,7 @@ EXPORTS.mozilla.intl += [
UNIFIED_SOURCES += [
'FluentBundle.cpp',
'FluentResource.cpp',
'L10nRegistry.cpp',
'Localization.cpp',
]
@ -26,6 +27,10 @@ TESTING_JS_MODULES += [
'FluentSyntax.jsm',
]
TEST_DIRS += [
'rust/gtest',
]
XPIDL_SOURCES += [
'mozILocalization.idl',
]

View File

@ -0,0 +1,14 @@
[package]
name = "l10nregistry-ffi-gtest"
version = "0.1.0"
authors = ["nobody@mozilla.org"]
license = "MPL-2.0"
description = "Tests for rust bindings to l10nRegistry"
edition = "2018"
[dependencies]
l10nregistry-ffi = { path = "../l10nregistry-ffi" }
moz_task = { path = "../../../../xpcom/rust/moz_task" }
[lib]
path = "test.rs"

View File

@ -0,0 +1,23 @@
/* 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/. */
#include "gtest/gtest.h"
extern "C" void Rust_L10NLoadAsync(bool* aItWorked);
TEST(RustL10N, LoadAsync)
{
bool itWorked = false;
Rust_L10NLoadAsync(&itWorked);
EXPECT_TRUE(itWorked);
}
extern "C" void Rust_L10NLoadSync(bool* aItWorked);
TEST(RustL10N, LoadSync)
{
bool itWorked = false;
Rust_L10NLoadSync(&itWorked);
EXPECT_TRUE(itWorked);
}

View File

@ -0,0 +1,11 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES += [
'Test.cpp',
]
FINAL_LIBRARY = 'xul-gtest'

View File

@ -0,0 +1,55 @@
/* 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/. */
use l10nregistry_ffi::{load_async, load_sync};
use moz_task;
use std::{
sync::atomic::{AtomicBool, Ordering::Relaxed},
sync::Arc,
};
#[no_mangle]
pub extern "C" fn Rust_L10NLoadAsync(it_worked: *mut bool) {
let done = Arc::new(AtomicBool::new(false));
let done2 = done.clone();
moz_task::spawn_current_thread(async move {
match load_async("resource://gre/localization/en-US/toolkit/about/aboutAbout.ftl").await {
Ok(res) => {
assert_eq!(res.len(), 460);
assert!(res.starts_with(
b"# This Source Code Form is subject to the terms of the Mozilla Public"
));
unsafe {
*it_worked = true;
}
}
Err(err) => println!("{:?}", err),
}
done.store(true, Relaxed);
})
.unwrap();
unsafe {
moz_task::gtest_only::spin_event_loop_until(move || done2.load(Relaxed)).unwrap();
*it_worked = true;
}
}
#[no_mangle]
pub extern "C" fn Rust_L10NLoadSync(it_worked: *mut bool) {
match load_sync("resource://gre/localization/en-US/toolkit/about/aboutAbout.ftl") {
Ok(res) => {
assert_eq!(res.len(), 460);
assert!(res.starts_with(
b"# This Source Code Form is subject to the terms of the Mozilla Public"
));
unsafe {
*it_worked = true;
}
}
Err(err) => println!("{:?}", err),
}
}

View File

@ -0,0 +1,11 @@
[package]
name = "l10nregistry-ffi"
version = "0.1.0"
authors = ["nobody@mozilla.org"]
edition = "2018"
[dependencies]
futures-channel = "0.3"
libc = "0.2"
nserror = { path = "../../../../xpcom/rust/nserror" }
nsstring = { path = "../../../../xpcom/rust/nsstring" }

View File

@ -0,0 +1,108 @@
/* 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/. */
use futures_channel::oneshot::channel;
use libc::c_void;
use nserror::nsresult;
use nsstring::{nsACString, nsCString, nsCStringLike};
use std::io::{self, Error, ErrorKind};
trait ToIoResult {
fn to_io_result(self) -> io::Result<()>;
}
impl ToIoResult for nsresult {
fn to_io_result(self) -> io::Result<()> {
if self.failed() {
return Err(Error::new(ErrorKind::Other, self));
} else {
Ok(())
}
}
}
mod ffi {
use super::*;
pub fn load(
path: &nsACString,
callback: Option<extern "C" fn(*mut c_void, *mut nsACString, nsresult)>,
closure: *mut c_void,
) -> io::Result<()> {
extern "C" {
fn L10nRegistryLoad(
aPath: *const nsACString,
callback: Option<extern "C" fn(*mut c_void, *mut nsACString, nsresult)>,
closure: *mut c_void,
) -> nsresult;
}
unsafe { L10nRegistryLoad(path, callback, closure) }.to_io_result()
}
pub fn load_sync(path: &nsACString, result: &mut nsACString) -> io::Result<()> {
extern "C" {
fn L10nRegistryLoadSync(aPath: *const nsACString, aRetVal: *mut nsACString) -> nsresult;
}
unsafe { L10nRegistryLoadSync(path, result) }.to_io_result()
}
/// Swap the memory to take ownership of the string data
#[allow(non_snake_case)]
pub fn take_nsACString(s: &mut nsACString) -> nsCString {
let mut result = nsCString::new();
result.take_from(s);
result
}
}
struct CallbackClosure {
cb: Box<dyn FnOnce(io::Result<&mut nsACString>)>,
}
impl CallbackClosure {
fn new<CB>(cb: CB) -> *mut Self
where
CB: FnOnce(io::Result<&mut nsACString>) + 'static,
{
let boxed = Box::new(Self {
cb: Box::new(cb) as Box<dyn FnOnce(io::Result<&mut nsACString>)>,
});
Box::into_raw(boxed)
}
fn call(self: Box<Self>, s: io::Result<&mut nsACString>) {
(self.cb)(s)
}
}
extern "C" fn load_async_cb(closure: *mut c_void, string: *mut nsACString, success: nsresult) {
let result = success.to_io_result().map(|_| unsafe { &mut *string });
let closure = unsafe {
debug_assert_ne!(closure, std::ptr::null_mut());
Box::from_raw(closure as *mut CallbackClosure)
};
closure.call(result);
}
pub async fn load_async(path: impl nsCStringLike) -> io::Result<nsCString> {
let (sender, receiver) = channel::<io::Result<nsCString>>();
let closure = CallbackClosure::new(move |result| {
let result = result.map(ffi::take_nsACString);
sender.send(result).expect("Failed to send result");
});
ffi::load(&*path.adapt(), Some(load_async_cb), closure as *mut c_void)?;
let result = receiver
.await
.map_err(|_| Error::new(ErrorKind::Interrupted, "canceled"))?;
result
}
pub fn load_sync(path: impl nsCStringLike) -> io::Result<nsCString> {
let mut result = nsCString::new();
ffi::load_sync(&*path.adapt(), &mut result)?;
Ok(result)
}

View File

@ -38,6 +38,7 @@ with_dbus = ["gkrust-shared/with_dbus"]
[dependencies]
bench-collections-gtest = { path = "../../../../xpcom/rust/gtest/bench-collections" }
l10nregistry-ffi-gtest = { path = "../../../../intl/l10n/rust/gtest" }
moz_task-gtest = { path = "../../../../xpcom/rust/gtest/moz_task" }
mp4parse-gtest = { path = "../../../../dom/media/gtest" }
nsstring-gtest = { path = "../../../../xpcom/rust/gtest/nsstring" }

View File

@ -8,6 +8,7 @@ extern crate fog_gtest;
#[cfg(feature = "libfuzzer")]
extern crate gecko_fuzz_targets;
extern crate gkrust_shared;
extern crate l10nregistry_ffi_gtest;
extern crate moz_task_gtest;
extern crate mp4parse_gtest;
extern crate nsstring_gtest;

View File

@ -65,6 +65,7 @@ rusqlite = { version = "0.24.1", features = ["modern_sqlite", "in_gecko"] }
fluent = { version = "0.13.1", features = ["fluent-pseudo"] }
fluent-ffi = { path = "../../../../intl/l10n/rust/fluent-ffi" }
firefox-accounts-bridge = { path = "../../../../services/fxaccounts/rust-bridge/firefox-accounts-bridge", optional=true }
l10nregistry-ffi = { path = "../../../../intl/l10n/rust/l10nregistry-ffi" }
processtools = { path = "../../../components/processtools" }
qcms = { path = "../../../../gfx/qcms" }

View File

@ -34,6 +34,7 @@ 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;