Source code

Revision control

Copy as Markdown

Other Tools

/* 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;
use nserror::{nsresult, NS_OK, NS_SUCCESS_ADOPTED_DATA};
use nsstring::{nsACString, nsCStringLike};
use std::{
cell::Cell,
ffi::c_void,
io::{self, Error, ErrorKind},
ptr,
};
use xpcom::{
interfaces::{nsIStreamLoader, nsIStreamLoaderObserver, nsISupports},
xpcom,
};
unsafe fn boxed_slice_from_raw(ptr: *mut u8, len: usize) -> Box<[u8]> {
if ptr.is_null() {
// It is undefined behaviour to create a `Box<[u8]>` with a null pointer,
// so avoid that case.
assert_eq!(len, 0);
Box::new([])
} else {
Box::from_raw(ptr::slice_from_raw_parts_mut(ptr, len))
}
}
#[xpcom(implement(nsIStreamLoaderObserver), nonatomic)]
struct StreamLoaderObserver {
sender: Cell<Option<oneshot::Sender<Result<Box<[u8]>, nsresult>>>>,
}
impl StreamLoaderObserver {
#[allow(non_snake_case)]
unsafe fn OnStreamComplete(
&self,
_loader: *const nsIStreamLoader,
_ctxt: *const nsISupports,
status: nsresult,
result_length: u32,
result: *const u8,
) -> nsresult {
let sender = match self.sender.take() {
Some(sender) => sender,
None => return NS_OK,
};
if status.failed() {
sender.send(Err(status)).expect("Failed to send data");
return NS_OK;
}
// safety: take ownership of the data passed in. This is OK because we
// have configured Rust and C++ to use the same allocator, and our
// caller won't free the `result` pointer if we return
// NS_SUCCESS_ADOPTED_DATA.
sender
.send(Ok(boxed_slice_from_raw(
result as *mut u8,
result_length as usize,
)))
.expect("Failed to send data");
NS_SUCCESS_ADOPTED_DATA
}
}
extern "C" {
fn L10nRegistryLoad(
path: *const nsACString,
observer: *const nsIStreamLoaderObserver,
) -> nsresult;
fn L10nRegistryLoadSync(
aPath: *const nsACString,
aData: *mut *mut c_void,
aSize: *mut u64,
) -> nsresult;
}
pub async fn load_async(path: impl nsCStringLike) -> io::Result<Box<[u8]>> {
let (sender, receiver) = oneshot::channel::<Result<Box<[u8]>, nsresult>>();
let observer = StreamLoaderObserver::allocate(InitStreamLoaderObserver {
sender: Cell::new(Some(sender)),
});
unsafe {
L10nRegistryLoad(&*path.adapt(), observer.coerce())
.to_result()
.map_err(|err| Error::new(ErrorKind::Other, err))?;
}
receiver
.await
.expect("Failed to receive from observer.")
.map_err(|err| Error::new(ErrorKind::Other, err))
}
pub fn load_sync(path: impl nsCStringLike) -> io::Result<Box<[u8]>> {
let mut data_ptr: *mut c_void = ptr::null_mut();
let mut data_length: u64 = 0;
unsafe {
L10nRegistryLoadSync(&*path.adapt(), &mut data_ptr, &mut data_length)
.to_result()
.map_err(|err| Error::new(ErrorKind::Other, err))?;
// The call succeeded, meaning `data_ptr` and `size` have been filled in with owning pointers to actual data payloads (or null).
// If we get a null, return a successful read of the empty file.
Ok(boxed_slice_from_raw(
data_ptr as *mut u8,
data_length as usize,
))
}
}