/* 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, 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> { let (sender, receiver) = oneshot::channel::, 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> { 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, )) } }