summaryrefslogtreecommitdiffstats
path: root/intl/l10n/rust/l10nregistry-ffi/src/load.rs
blob: 04a041246f2d2c2ff8913d4dfe29313325f33f79 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* 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,
        ))
    }
}